Search code examples
pythonnumpyscipy

Attribute error when updating covariance of scipy.stats.multivariate_normal


I'd like to sequentially update a covariance matrix of a multivariate Gaussian distribution, but getting an AttributeError. Below is a simple piece of code to reproduce the error:

import numpy as np
from scipy.stats import multivariate_normal
# Initial mean 
init_pose = np.array([0, 0, 0]).T

# Multi-variate Gaussian 
belief = multivariate_normal(mean=init_pose, cov=np.diag([1e-10, 1e-10, 1e-10])) 
print(belief.mean, belief.cov)

# Update mean and covariance 
belief.mean = np.array([10, 20, 30]).T
belief.cov = np.diag([1, 2, 3])

The error messages are:

AttributeError                            Traceback (most recent call last)
Cell In[42], line 7
      5 print(belief.mean, belief.cov)
      6 belief.mean = np.array([10, 20, 30]).T
----> 7 belief.cov = np.diag([1, 2, 3])
      8 print(belief.mean, belief.cov)

AttributeError: can't set attribute

I checked the attributes of the variable, belief, by using belief.__dict__, which gives the following:

{'_dist': <scipy.stats._multivariate.multivariate_normal_gen object at 0x2b84d81cfc10>,
 'dim': 3, 'mean': array([0., 0., 0.]), 
'cov_object': <scipy.stats._covariance.CovViaPSD object at 0x2b84d81cfbe0>, 
'allow_singular': False, 'maxpts': 3000000, 'abseps': 1e-05, 'releps': 1e-05}

As the error suggests, the variable doesn't have cov attribute but has cov_object instead. Is there any way to update the covariance using a numpy array?


Solution

  • Updating the covariance matrix is not officially supported in the existing infrastructure. cov is a read-only attribute as you can see in the source. (This is not documented, though, and that is one of many known issues with the multivariate distributions.)

    You can (although not officially supported) update the cov_object attribute you found. If you use stats.Covariance, which I'd recommend, this might look like:

    import numpy as np
    from scipy.stats import multivariate_normal, Covariance
    
    x = [1, 1, 1]
    
    # Initial mean and covariance
    init_pose = np.array([0, 0, 0]).T
    cov = Covariance.from_diagonal(np.array([1, 2, 3]))
    
    # Multi-variate Gaussian 
    belief = multivariate_normal(mean=init_pose, cov=cov) 
    belief.pdf(x)  # 0.01036457019516129
    
    # Update covariance
    cov = Covariance.from_diagonal(np.array([2, 4, 6]))
    belief.cov_object = cov
    belief.pdf(x)  # 0.005795060058469202
    # same as
    # multivariate_normal(mean=init_pose, cov=cov).pdf(x)
    

    As long as your new mean and cov_object don't violate the conditions you can infer from _process_parameters_Covariance (basically just consistent dimensionality), I expect it to work correctly. If you change the mean and cov_object to represent a multivariate normal in a different number of dimensions, you'll also need to update the dim attribute accordingly.


    In the comments, you wrote:

    However, I don't know how to define a non-diagonal covariance matrix.

    Covariance has several other options for defining your covariance matrix. The SciPy documentation appears to have certificate issues ATM, but you will find other methods like from_precision, from_cholesky, and from_eigendecomposition that will work for more general covariance matrices.

    So, does that mean a value cannot be set to the covariance in this way because the covariance is a property and not an attribute?

    Well, it's because it's a read-only attribute created using the @property decoratory; it's not a normal attribute defined in, say, the __init__ method like self.cov = cov.