nchopin / particles

Sequential Monte Carlo in python
MIT License
395 stars 74 forks source link

Problem with variance estimator #89

Closed SchroederAdrian closed 2 weeks ago

SchroederAdrian commented 1 month ago

Hello Professor Chopin,

Thanks for the excellent package and your hard work! Your write up of the package and ease of modification is incredible.

I ran into a bug when I was trying to employ the variance estimator, namely when calling

from particles import variance_estimators as var
fk = ssms.Bootstrap(ssm=LinStSp(lambda_i=monte_lambda, S=monte_S, ini_fac=monte_f0), data=y_monte)
pf = particles.SMC(fk=fk, N=R, qmc=False, store_history=True, resampling='systematic', collect=[particles.collectors.Moments(), var.Var()])
pf.run()
paths = pf.hist.backward_sampling_mcmc(M=R)

It appears that Var() creates a typing error. I tried with or without storing history and partial history and without the second collector but the following error persists:

Input [In [61]](vscode-notebook-cell:?execution_count=61), in <cell line: 4>()
      [2](vscode-notebook-cell:?execution_count=61&line=2) fk = ssms.Bootstrap(ssm=LinStSp(lambda_i=monte_lambda, S=monte_S, ini_fac=monte_f0), data=y_monte)
      [3](vscode-notebook-cell:?execution_count=61&line=3) pf = particles.SMC(fk=fk, N=R, qmc=False, store_history=True, resampling='systematic', collect=[particles.collectors.Moments(), var.Var()])
----> [4](vscode-notebook-cell:?execution_count=61&line=4) pf.run()
      [5](vscode-notebook-cell:?execution_count=61&line=5) # paths = pf.hist.X
      [6](vscode-notebook-cell:?execution_count=61&line=6) paths = pf.hist.backward_sampling_mcmc(M=R)

File ~/.local/lib/python3.9/site-packages/particles/utils.py:85, in timer.<locals>.timed_method(self, **kwargs)
     [82](https://file+.vscode-resource.vscode-cdn.net/Users/Adrian/Documents/Research/Latent%20State%20Models/SMC_implement/~/.local/lib/python3.9/site-packages/particles/utils.py:82) @functools.wraps(method)
     [83](https://file+.vscode-resource.vscode-cdn.net/Users/Adrian/Documents/Research/Latent%20State%20Models/SMC_implement/~/.local/lib/python3.9/site-packages/particles/utils.py:83) def timed_method(self, **kwargs):
     [84](https://file+.vscode-resource.vscode-cdn.net/Users/Adrian/Documents/Research/Latent%20State%20Models/SMC_implement/~/.local/lib/python3.9/site-packages/particles/utils.py:84)     starting_time = time.perf_counter()
---> [85](https://file+.vscode-resource.vscode-cdn.net/Users/Adrian/Documents/Research/Latent%20State%20Models/SMC_implement/~/.local/lib/python3.9/site-packages/particles/utils.py:85)     out = method(self, **kwargs)
     [86](https://file+.vscode-resource.vscode-cdn.net/Users/Adrian/Documents/Research/Latent%20State%20Models/SMC_implement/~/.local/lib/python3.9/site-packages/particles/utils.py:86)     self.cpu_time = time.perf_counter() - starting_time
     [87](https://file+.vscode-resource.vscode-cdn.net/Users/Adrian/Documents/Research/Latent%20State%20Models/SMC_implement/~/.local/lib/python3.9/site-packages/particles/utils.py:87)     return out

File ~/.local/lib/python3.9/site-packages/particles/core.py:422, in SMC.run(self)
    [405](https://file+.vscode-resource.vscode-cdn.net/Users/Adrian/Documents/Research/Latent%20State%20Models/SMC_implement/~/.local/lib/python3.9/site-packages/particles/core.py:405) @utils.timer
    [406](https://file+.vscode-resource.vscode-cdn.net/Users/Adrian/Documents/Research/Latent%20State%20Models/SMC_implement/~/.local/lib/python3.9/site-packages/particles/core.py:406) def run(self):
    [407](https://file+.vscode-resource.vscode-cdn.net/Users/Adrian/Documents/Research/Latent%20State%20Models/SMC_implement/~/.local/lib/python3.9/site-packages/particles/core.py:407)     """Runs particle filter until completion.
    [408](https://file+.vscode-resource.vscode-cdn.net/Users/Adrian/Documents/Research/Latent%20State%20Models/SMC_implement/~/.local/lib/python3.9/site-packages/particles/core.py:408) 
    [409](https://file+.vscode-resource.vscode-cdn.net/Users/Adrian/Documents/Research/Latent%20State%20Models/SMC_implement/~/.local/lib/python3.9/site-packages/particles/core.py:409)     Note: this class implements the iterator protocol. This makes it
   (...)
    [420](https://file+.vscode-resource.vscode-cdn.net/Users/Adrian/Documents/Research/Latent%20State%20Models/SMC_implement/~/.local/lib/python3.9/site-packages/particles/core.py:420)     command only.
    [421](https://file+.vscode-resource.vscode-cdn.net/Users/Adrian/Documents/Research/Latent%20State%20Models/SMC_implement/~/.local/lib/python3.9/site-packages/particles/core.py:421)     """
--> [422](https://file+.vscode-resource.vscode-cdn.net/Users/Adrian/Documents/Research/Latent%20State%20Models/SMC_implement/~/.local/lib/python3.9/site-packages/particles/core.py:422)     for _ in self:
    [423](https://file+.vscode-resource.vscode-cdn.net/Users/Adrian/Documents/Research/Latent%20State%20Models/SMC_implement/~/.local/lib/python3.9/site-packages/particles/core.py:423)         pass

File ~/.local/lib/python3.9/site-packages/particles/core.py:396, in SMC.__next__(self)
    [394](https://file+.vscode-resource.vscode-cdn.net/Users/Adrian/Documents/Research/Latent%20State%20Models/SMC_implement/~/.local/lib/python3.9/site-packages/particles/core.py:394)         self.resample_move()
    [395](https://file+.vscode-resource.vscode-cdn.net/Users/Adrian/Documents/Research/Latent%20State%20Models/SMC_implement/~/.local/lib/python3.9/site-packages/particles/core.py:395) self.reweight_particles()
--> [396](https://file+.vscode-resource.vscode-cdn.net/Users/Adrian/Documents/Research/Latent%20State%20Models/SMC_implement/~/.local/lib/python3.9/site-packages/particles/core.py:396) self.compute_summaries()
    [397](https://file+.vscode-resource.vscode-cdn.net/Users/Adrian/Documents/Research/Latent%20State%20Models/SMC_implement/~/.local/lib/python3.9/site-packages/particles/core.py:397) self.t += 1

File ~/.local/lib/python3.9/site-packages/particles/core.py:381, in SMC.compute_summaries(self)
    [378](https://file+.vscode-resource.vscode-cdn.net/Users/Adrian/Documents/Research/Latent%20State%20Models/SMC_implement/~/.local/lib/python3.9/site-packages/particles/core.py:378) # must collect summaries *after* history, because a collector (e.g.
    [379](https://file+.vscode-resource.vscode-cdn.net/Users/Adrian/Documents/Research/Latent%20State%20Models/SMC_implement/~/.local/lib/python3.9/site-packages/particles/core.py:379) # FixedLagSmoother) may needs to access history
    [380](https://file+.vscode-resource.vscode-cdn.net/Users/Adrian/Documents/Research/Latent%20State%20Models/SMC_implement/~/.local/lib/python3.9/site-packages/particles/core.py:380) if self.summaries:
--> [381](https://file+.vscode-resource.vscode-cdn.net/Users/Adrian/Documents/Research/Latent%20State%20Models/SMC_implement/~/.local/lib/python3.9/site-packages/particles/core.py:381)     self.summaries.collect(self)

File ~/.local/lib/python3.9/site-packages/particles/collectors.py:231, in Summaries.collect(self, smc)
    [229](https://file+.vscode-resource.vscode-cdn.net/Users/Adrian/Documents/Research/Latent%20State%20Models/SMC_implement/~/.local/lib/python3.9/site-packages/particles/collectors.py:229) def collect(self, smc):
    [230](https://file+.vscode-resource.vscode-cdn.net/Users/Adrian/Documents/Research/Latent%20State%20Models/SMC_implement/~/.local/lib/python3.9/site-packages/particles/collectors.py:230)     for col in self._collectors:
--> [231](https://file+.vscode-resource.vscode-cdn.net/Users/Adrian/Documents/Research/Latent%20State%20Models/SMC_implement/~/.local/lib/python3.9/site-packages/particles/collectors.py:231)         col.collect(smc)

File ~/.local/lib/python3.9/site-packages/particles/collectors.py:271, in Collector.collect(self, smc)
    [270](https://file+.vscode-resource.vscode-cdn.net/Users/Adrian/Documents/Research/Latent%20State%20Models/SMC_implement/~/.local/lib/python3.9/site-packages/particles/collectors.py:270) def collect(self, smc):
--> [271](https://file+.vscode-resource.vscode-cdn.net/Users/Adrian/Documents/Research/Latent%20State%20Models/SMC_implement/~/.local/lib/python3.9/site-packages/particles/collectors.py:271)     self.summary.append(self.fetch(smc))

File ~/.local/lib/python3.9/site-packages/particles/variance_estimators.py:169, in Var.fetch(self, smc)
    [167](https://file+.vscode-resource.vscode-cdn.net/Users/Adrian/Documents/Research/Latent%20State%20Models/SMC_implement/~/.local/lib/python3.9/site-packages/particles/variance_estimators.py:167) def fetch(self, smc):
    [168](https://file+.vscode-resource.vscode-cdn.net/Users/Adrian/Documents/Research/Latent%20State%20Models/SMC_implement/~/.local/lib/python3.9/site-packages/particles/variance_estimators.py:168)     self.update_B(smc)
--> [169](https://file+.vscode-resource.vscode-cdn.net/Users/Adrian/Documents/Research/Latent%20State%20Models/SMC_implement/~/.local/lib/python3.9/site-packages/particles/variance_estimators.py:169)     return var_estimate(smc.W, self.test_func(smc.X), self.B)

File ~/.local/lib/python3.9/site-packages/particles/variance_estimators.py:129, in var_estimate(W, phi_x, B)
    [127](https://file+.vscode-resource.vscode-cdn.net/Users/Adrian/Documents/Research/Latent%20State%20Models/SMC_implement/~/.local/lib/python3.9/site-packages/particles/variance_estimators.py:127)     out = np.zeros_like(m)
    [128](https://file+.vscode-resource.vscode-cdn.net/Users/Adrian/Documents/Research/Latent%20State%20Models/SMC_implement/~/.local/lib/python3.9/site-packages/particles/variance_estimators.py:128) else:
--> [129](https://file+.vscode-resource.vscode-cdn.net/Users/Adrian/Documents/Research/Latent%20State%20Models/SMC_implement/~/.local/lib/python3.9/site-packages/particles/variance_estimators.py:129)     out = _sum_over_branches(w_phi, B)
    [130](https://file+.vscode-resource.vscode-cdn.net/Users/Adrian/Documents/Research/Latent%20State%20Models/SMC_implement/~/.local/lib/python3.9/site-packages/particles/variance_estimators.py:130) return out

File ~/.local/lib/python3.9/site-packages/numba/core/dispatcher.py:468, in _DispatcherBase._compile_for_args(self, *args, **kws)
    [464](https://file+.vscode-resource.vscode-cdn.net/Users/Adrian/Documents/Research/Latent%20State%20Models/SMC_implement/~/.local/lib/python3.9/site-packages/numba/core/dispatcher.py:464)         msg = (f"{str(e).rstrip()} \n\nThis error may have been caused "
    [465](https://file+.vscode-resource.vscode-cdn.net/Users/Adrian/Documents/Research/Latent%20State%20Models/SMC_implement/~/.local/lib/python3.9/site-packages/numba/core/dispatcher.py:465)                f"by the following argument(s):\n{args_str}\n")
    [466](https://file+.vscode-resource.vscode-cdn.net/Users/Adrian/Documents/Research/Latent%20State%20Models/SMC_implement/~/.local/lib/python3.9/site-packages/numba/core/dispatcher.py:466)         e.patch_message(msg)
--> [468](https://file+.vscode-resource.vscode-cdn.net/Users/Adrian/Documents/Research/Latent%20State%20Models/SMC_implement/~/.local/lib/python3.9/site-packages/numba/core/dispatcher.py:468)     error_rewrite(e, 'typing')
    [469](https://file+.vscode-resource.vscode-cdn.net/Users/Adrian/Documents/Research/Latent%20State%20Models/SMC_implement/~/.local/lib/python3.9/site-packages/numba/core/dispatcher.py:469) except errors.UnsupportedError as e:
    [470](https://file+.vscode-resource.vscode-cdn.net/Users/Adrian/Documents/Research/Latent%20State%20Models/SMC_implement/~/.local/lib/python3.9/site-packages/numba/core/dispatcher.py:470)     # Something unsupported is present in the user code, add help info
    [471](https://file+.vscode-resource.vscode-cdn.net/Users/Adrian/Documents/Research/Latent%20State%20Models/SMC_implement/~/.local/lib/python3.9/site-packages/numba/core/dispatcher.py:471)     error_rewrite(e, 'unsupported_error')

File ~/.local/lib/python3.9/site-packages/numba/core/dispatcher.py:409, in _DispatcherBase._compile_for_args.<locals>.error_rewrite(e, issue_type)
    [407](https://file+.vscode-resource.vscode-cdn.net/Users/Adrian/Documents/Research/Latent%20State%20Models/SMC_implement/~/.local/lib/python3.9/site-packages/numba/core/dispatcher.py:407)     raise e
    [408](https://file+.vscode-resource.vscode-cdn.net/Users/Adrian/Documents/Research/Latent%20State%20Models/SMC_implement/~/.local/lib/python3.9/site-packages/numba/core/dispatcher.py:408) else:
--> [409](https://file+.vscode-resource.vscode-cdn.net/Users/Adrian/Documents/Research/Latent%20State%20Models/SMC_implement/~/.local/lib/python3.9/site-packages/numba/core/dispatcher.py:409)     raise e.with_traceback(None)

TypingError: Failed in nopython mode pipeline (step: nopython frontend)
No implementation of function Function(<built-in function iadd>) found for signature:

 >>> iadd(float64, array(float64, 1d, C))

There are 18 candidate implementations:
  - Of which 16 did not match due to:
  Overload of function 'iadd': File: <numerous>: Line N/A.
    With argument(s): '(float64, array(float64, 1d, C))':
   No match.
  - Of which 2 did not match due to:
  Operator Overload in function 'iadd': File: unknown: Line unknown.
    With argument(s): '(float64, array(float64, 1d, C))':
   No match for registered cases:
    * (int64, int64) -> int64
    * (int64, uint64) -> int64
    * (uint64, int64) -> int64
    * (uint64, uint64) -> uint64
    * (float32, float32) -> float32
    * (float64, float64) -> float64
    * (complex64, complex64) -> complex64
    * (complex128, complex128) -> complex128

During: typing of intrinsic-call at /Users/Adrian/.local/lib/python3.9/site-packages/particles/variance_estimators.py (138)

File "../../../../.local/lib/python3.9/site-packages/particles/variance_estimators.py", line 138:
def _sum_over_branches(w_phi, B):
    <source elided>
    for m in range(N):
        s[B[m]] += w_phi[m]
        ^

I'm on an M2 Mac with the new arm64 architecture and use Python 3.9 on VS Code, not sure if it's a software issue on my end?

Thank you! Adrian

nchopin commented 1 month ago

Weird... Please upload a MWE (minimal working example), so that I can try to see what is going on my end. (The first box is not a MWE, I don't know how you defined LinStSp). If you can make it as small as possible, that'd be great.

SchroederAdrian commented 1 month ago

Ah yes apologies, here the parameters and the model, essentially a multivariate linear Gaussian model with transform matrix lambda, VAR lag S and some parameter restrictions.

import numpy as np
import particles
from particles import distributions as dists
from particles import state_space_models as ssms
Q=2 # dimension latent variable
N=100 # dimension series
R=1000 # number particles
T = 50 # time periods

def generate_S(Q):
    if Q > 1:
        s = np.random.uniform(-0.9, 0.9, size=Q)
        s = np.array(s / np.sum(np.abs(s)), ndmin=2)
        S = s.T @ s
        return S
    else:
        return np.array(np.random.uniform(-0.8, 0.8), ndmin=2)
monte_S = generate_S(Q)

def construct_lambda(N, Q):
    # constructs a coefficient matrix satisfying the identification restrictions
    eigenvalues = np.random.uniform(low=2.0, high=10.0, size=Q)  # distinct, non-zero eigenvalues
    Lambda = np.diag(eigenvalues)
    U, _ = np.linalg.qr(np.random.normal(size=(N, Q)))
    A = U @ np.sqrt(Lambda)
    return A
monte_lambda = construct_lambda(N, Q)
monte_f0 = np.zeros((Q,1)) # initial value

class LinStSp(ssms.StateSpaceModel):
    default_params = {'lambda_i': np.random.normal(size=(N,Q)), 'S': np.eye(Q), 'ini_fac': np.zeros(Q)}

    def PX0(self):
        return dists.MvNormal(loc = self.ini_fac.T , cov=np.eye(Q))

    def PX(self, t, xp):
        mean = self.S @ xp.T # will be Q x R 
        return dists.MvNormal(loc = mean.T, cov=np.eye(Q)) # and transpose then R x Q

    def PY(self, t, xp, x):
        meanY = self.lambda_i @ x.T # + self.beta # will be N x R
        return dists.MvNormal(loc = meanY.T, cov=np.eye(N))

# simulate data
model_fil = LinStSp(lambda_i = monte_lambda, S= monte_S, ini_fac = monte_f0)
x_monte, y_monte = model_fil.simulate(T+200)
x_monte, y_monte = x_monte[200:], y_monte[200:]

from particles import variance_estimators as var
fk = ssms.Bootstrap(ssm=LinStSp(lambda_i=monte_lambda, S=monte_S, ini_fac=monte_f0), data=y_monte)
pf = particles.SMC(fk=fk, N=R, qmc=False, store_history=True, resampling='systematic', collect=[particles.collectors.Moments(), var.Var()])
pf.run()
# paths = pf.hist.X
paths = pf.hist.backward_sampling_mcmc(M=R)
nchopin commented 1 month ago

The scary message from Numba was actually hiding a simple bug, which triggered an error only when function $\varphi$ is such that $\varphi(x)$ is a vector (instead of a scalar); in your case $\varphi(x)=x$ (default function), and your $x$ is of vector of dimension 2. It's now fixed in the experimental branch. I'll run more tests when I'm back from holiday, before pushing the fix to the master branch. Please switch to experimental in the meantime. And thanks a lot for spotting this and reporting it here.

SchroederAdrian commented 1 month ago

Ah that's what it was I already suspected the Numba in my virtual env. Thanks so much for looking into this so quickly, appreciate it. And enjoy the rest of your holiday!