pythonstatisticspymc3

pymc3 TypeError: Unsupported dtype for TensorType: object


I am trying to determine the parameters mu1, mu2, sigma1, sigma2, and w of a bimodal distribution using pymc3. I use the following code:

# Generate sample data
import numpy as np
from pylab import concatenate, normal

# First normal distribution parameters
mu1 = 1
sigma1 = 0.1

# Second normal distribution parameters
mu2 = 2
sigma2 = 0.2

w1 = 2/3 # Proportion of samples from first distribution
w2 = 1/3 # Proportion of samples from second distribution

n = 7500 # Total number of samples

n1 = int(n*w1) # Number of samples from first distribution
n2 = int(n*w2) # Number of samples from second distribution

# Generate n1 samples from the first normal distribution and n2 samples from the second normal distribution
y = concatenate((normal(mu1, sigma1, n1), normal(mu2, sigma2, n2)))


# Recover parameters mu1, sigma1, mu2, sigma2 and w from the sample data
import pymc3 as pm
from scipy.stats import norm
model = pm.Model()

def logp(mu, sigma, w, y):
  mu1 = mu[0]
  mu2 = mu[1]
  sigma1 = sigma[0]
  sigma2 = sigma[1]
  return np.log(w * norm.pdf(y, mu1, sigma1) + (1-w) * norm.pdf(y, mu2, sigma2)).sum()

with model:

  # Priors for unknown model parameters
  mu = pm.Normal("mu", mu=0, sigma=1, shape=2)
  sigma = pm.HalfNormal("sigma", sigma=1, shape=2)
  w = pm.distributions.continuous.Uniform("w")

  # Likelihood (sampling distribution) of observations
  likelihood=pm.DensityDist('Likelihood', logp, observed=dict(mu=mu, sigma=sigma, w=w, y=y))

start = pm.find_MAP(model = model)

The following is the error stack trace:

KeyError                                  Traceback (most recent call last)
/usr/local/lib/python3.7/dist-packages/theano/tensor/type.py in dtype_specs(self)
    279                 "complex64": (complex, "theano_complex64", "NPY_COMPLEX64"),
--> 280             }[self.dtype]
    281         except KeyError:

KeyError: 'object'

During handling of the above exception, another exception occurred:

TypeError                                 Traceback (most recent call last)
12 frames
<ipython-input-30-12971af8f9da> in <module>()
     25 
     26   # Likelihood (sampling distribution) of observations
---> 27   likelihood=pm.DensityDist('Likelihood', logp, observed=dict(mu=mu, sigma=sigma, w=w, y=y))
     28 
     29 start = pm.find_MAP(model = model)

/usr/local/lib/python3.7/dist-packages/pymc3/distributions/distribution.py in __new__(cls, name, *args, **kwargs)
    120         else:
    121             dist = cls.dist(*args, **kwargs)
--> 122         return model.Var(name, dist, data, total_size, dims=dims)
    123 
    124     def __getnewargs__(self):

/usr/local/lib/python3.7/dist-packages/pymc3/model.py in Var(self, name, dist, data, total_size, dims)
   1165                     distribution=dist,
   1166                     total_size=total_size,
-> 1167                     model=self,
   1168                 )
   1169             self.observed_RVs.append(var)

/usr/local/lib/python3.7/dist-packages/pymc3/model.py in __init__(self, name, data, distribution, total_size, model)
   1871             datum.missing_values for datum in self.data.values() if datum.missing_values is not None
   1872         ]
-> 1873         self.logp_elemwiset = distribution.logp(**self.data)
   1874         # The logp might need scaling in minibatches.
   1875         # This is done in `Factor`.

<ipython-input-30-12971af8f9da> in logp(mu, sigma, w, y)
     15   sigma1 = sigma[0]
     16   sigma2 = sigma[1]
---> 17   return np.log(w * norm.pdf(y, mu1, sigma1) + (1-w) * norm.pdf(y, mu2, sigma2)).sum()
     18 
     19 with basic_model:

/usr/local/lib/python3.7/dist-packages/scipy/stats/_distn_infrastructure.py in pdf(self, x, *args, **kwds)
   1738         args = tuple(map(asarray, args))
   1739         dtyp = np.find_common_type([x.dtype, np.float64], [])
-> 1740         x = np.asarray((x - loc)/scale, dtype=dtyp)
   1741         cond0 = self._argcheck(*args) & (scale > 0)
   1742         cond1 = self._support_mask(x, *args) & (scale > 0)

/usr/local/lib/python3.7/dist-packages/theano/tensor/var.py in __truediv__(self, other)
    168 
    169     def __truediv__(self, other):
--> 170         return theano.tensor.basic.true_div(self, other)
    171 
    172     def __floordiv__(self, other):

/usr/local/lib/python3.7/dist-packages/theano/graph/op.py in __call__(self, *inputs, **kwargs)
    248         """
    249         return_list = kwargs.pop("return_list", False)
--> 250         node = self.make_node(*inputs, **kwargs)
    251 
    252         if config.compute_test_value != "off":

/usr/local/lib/python3.7/dist-packages/theano/tensor/elemwise.py in make_node(self, *inputs)
    497         using DimShuffle.
    498         """
--> 499         inputs = list(map(as_tensor_variable, inputs))
    500         out_dtypes, out_broadcastables, inputs = self.get_output_info(
    501             DimShuffle, *inputs

/usr/local/lib/python3.7/dist-packages/theano/tensor/basic.py in as_tensor_variable(x, name, ndim)
    205         )
    206 
--> 207     return constant(x, name=name, ndim=ndim)
    208 
    209 

/usr/local/lib/python3.7/dist-packages/theano/tensor/basic.py in constant(x, name, ndim, dtype)
    253         assert x_.ndim == ndim
    254 
--> 255     ttype = TensorType(dtype=x_.dtype, broadcastable=[s == 1 for s in x_.shape])
    256 
    257     try:

/usr/local/lib/python3.7/dist-packages/theano/tensor/type.py in __init__(self, dtype, broadcastable, name, sparse_grad)
     52         # True or False
     53         self.broadcastable = tuple(bool(b) for b in broadcastable)
---> 54         self.dtype_specs()  # error checking is done there
     55         self.name = name
     56         self.numpy_dtype = np.dtype(self.dtype)

/usr/local/lib/python3.7/dist-packages/theano/tensor/type.py in dtype_specs(self)
    281         except KeyError:
    282             raise TypeError(
--> 283                 f"Unsupported dtype for {self.__class__.__name__}: {self.dtype}"
    284             )
    285 

TypeError: Unsupported dtype for TensorType: object

Updated the code as per accepted answer. Crash no longer happens but I do not get a convergence towards the solution:

import pymc3 as pm
import numpy as np
from pylab import concatenate, normal

# Generate sample data

# First normal distribution parameters
mu1 = 1
sigma1 = 0.1

# Second normal distribution parameters
mu2 = 2
sigma2 = 0.2

w1 = 2/3 # Proportion of samples from first distribution
w2 = 1/3 # Proportion of samples from second distribution

n = 7500 # Total number of samples

n1 = int(n*w1) # Number of samples from first distribution
n2 = int(n*w2) # Number of samples from second distribution

# Generate n1 samples from the first normal distribution and n2 samples from the second normal distribution
y = concatenate((normal(mu1, sigma1, n1), normal(mu2, sigma2, n2)))

def normpdf(y, mu, sigma):
    return (1 / sigma) * pm.math.exp(pow(y - mu, 2) / pow(sigma, 2))

def logp(mu, sigma, w, y):
    mu1 = mu[0]
    mu2 = mu[1]
    sigma1 = sigma[0]
    sigma2 = sigma[1]
    w1 = w[0]
    w2 = w[1]
    return pm.math.sum(np.log(w1 * normpdf(y, mu1, sigma1) + w2 * normpdf(y, mu2, sigma2)))

# Recover parameters mu1, sigma1, mu2, sigma2 and w from the sample data
model = pm.Model()

with model:
    # Priors for unknown model parameters
    mu = pm.Normal("mu", mu=np.array([0.5, 1.5]), sigma=1, shape=2)
    sigma = pm.HalfNormal("sigma", sigma=1, shape=2)
    w = pm.Dirichlet("w", a=np.array([1.1, 0.9]), shape=2)
    
    # Likelihood (sampling distribution) of observations
    likelihood = pm.DensityDist(
        'Likelihood', logp, observed=dict(mu=mu, sigma=sigma, w=w, y=y))

start = pm.find_MAP(model=model)
print(start)

Output:

{'mu': array([-2.12059455,  1.95271142]),
 'sigma': array([0.01268331, 0.37437335]),
 'sigma_log__': array([-4.36746845, -0.98250172]),
 'w': array([0.70625152, 0.29374848]),
 'w_stickbreaking__': array([0.43862378])}

Solution

  • Using pdb I stepped through and found that the error was on this line:

    return np.log(w * norm.pdf(y, mu1, sigma1) + (1-w) * norm.pdf(y, mu2, sigma2)).sum()

    because scipy.stats.norm doesn't know how to deal with pymc/Theano objects. While I'm sure there's an easy way to get a normal density from pymc3 objects, it's also straightforward to define a function to calculate it ourselves:

    def normpdf(y, mu, sigma):
        return pm.math.sum((1 / sigma) * pm.math.exp(pow(y - mu, 2) / pow(sigma, 2)))
    
    def logp(mu, sigma, w, y):
        mu1 = mu[0]
        mu2 = mu[1]
        sigma1 = sigma[0]
        sigma2 = sigma[1]
        return np.log(w * normpdf(y, mu1, sigma1) + (1-w) * normpdf(y, mu2, sigma2)).sum()
    
    # Recover parameters mu1, sigma1, mu2, sigma2 and w from the sample data
    model = pm.Model()
    
    with model:
        # Priors for unknown model parameters
        mu = pm.Normal("mu", mu=0, sigma=1, shape=2)
        sigma = pm.HalfNormal("sigma", sigma=1, shape=2)
        #   w = pm.distributions.continuous.Uniform("w")
        w = pm.Dirichlet("p", a=np.array([1.0, 1.0]), shape=2)
        # Likelihood (sampling distribution) of observations
        likelihood = pm.DensityDist(
            'Likelihood', logp, observed=dict(mu=mu, sigma=sigma, w=w, y=y))
    
    
    start = pm.find_MAP(model=model)
    

    This should work; I've not inspected the output but it at least compiles and runs without error.