GithubHelp home page GithubHelp logo

pfilter's Introduction

pfilter

Basic Python particle filter. Plain SIR filtering, with various resampling algorithms. Written to be simple and clear; not necessarily most efficient or most flexible implementation. Depends on NumPy only.

Uses

This repo is useful for understanding how a particle filter works, or a quick way to develop a custom filter of your own from a relatively simple codebase.

Alternatives

There are more mature and sophisticated packages for probabilistic filtering in Python (especially for Kalman filtering) if you want an off-the-shelf solution:

Particle filtering

  • particles Extensive particle filtering, including smoothing and quasi-SMC algorithms
  • FilterPy Provides extensive Kalman filtering and basic particle filtering.
  • pyfilter provides Unscented Kalman Filtering, Sequential Importance Resampling and Auxiliary Particle Filter models, and has a number of advanced algorithms implemented, with PyTorch backend.

Kalman filtering

  • pykalman Easy to use Kalman Filter, Extended Kalman Filter and Unscented Kalman Filter implementations
  • simdkalman Fast implmentations of plain Kalman filter banks.
  • torch-kalman PyTorch implementation of Kalman filters, including Pandas dataframe support.

Installation

Available via PyPI:

pip install pfilter

Or install the git version:

pip install git+https://github.com/johnhw/pfilter.git

Usage

Create a ParticleFilter object, then call update(observation) with an observation array to update the state of the particle filter.

Calling update() without an observation will update the model without any data, i.e. perform a prediction step only.

Model

  • Internal state space of d dimensions
  • Observation space of h dimensions
  • n particles estimating state in each time step

Particles are represented as an (n,d) matrix of states, one state per row. Observations are generated from this matrix into an (n,h) matrix of hypothesized observations via the observation function.

Functions

You need to specify at the minimum:

  • an observation function observe_fn(state (n,d)) => observation matrix (n,h) which will return a predicted observation for an internal state.
  • a function that samples from an initial distributions prior_fn => state matrix (n,d) for all of the internal state variables. These are usually distributions from scipy.stats. The utility function independent_sample makes it easy to concatenate sampling functions to sample the whole state vector.
  • a weight function weight_fn(hyp_observed (n,h), real_observed (h,)) => weight vector (n,) which specifies how well each of the hyp_observed arrays match the real observation real_observed. This must produce a strictly positive weight value for each hypothesized observation, where larger means more similar. This is often an RBF kernel or similar.

Typically, you would also specify:

  • dynamics a function dynamics_fn(state (n,d)) => predicted_state (n,d) to update the state based on internal (forward prediction) dynamics, and a
  • diffusion a function noise_fn(predicted_state (n,d)) => noisy_state (n,d) to add diffusion into the sampling process (though you could also merge into the dynamics).

You might also specify:

  • Internal weighting a function internal_weight_fn(state (n,d)) => weight vector (n,) which provides a weighting to apply on top of the weight function based on internal state. This is useful to impose penalties or to include learned inverse models in the inference.
  • Post-processing transform function a function transform_fn(state (n,d), weights (n,)) => states(n, k) which can apply a post-processing transform and store the result in transformed_particles

Missing observations

If you want to be able to deal with partial missing values in the observations, the weight function should support masked arrays. The squared_error(a,b) function in pfilter.py does this, for example.

Passing values to functions

Sometimes it is useful to pass inputs to callback functions like dynamics_fn(x) at each time step. You can do this by giving keyword arguments to update().

If you call pf.update(y, t=5) all of the functions dynamics_fn, weight_fn, noise_fn, internal_weight_fn, observe_fn will receive the keyword argument t=5. ALl kwargs are forwarded to these calls. You can just ignore them if not used (e.g. define dynamics_fn = lambda x, **kwargs: real_dynamics(x)) but this can be useful for propagating inputs that are neither internal states nor observed states to the filter. If no kwargs are given to update, then no extra arguments are passed to any of callbacks.

Attributes

The ParticleFilter object will have the following useful attributes after updating:

  • original_particles the (n,d) collection of particles in the last update step
  • mean_state the (d,) expectation of the state
  • mean_hypothesized the (h,) expectation of the hypothesized observations
  • cov_state the (d,d) covariance matrix of the state
  • map_state the (d,) most likely state
  • map_hypothesized the (h,) most likely hypothesized observation
  • weights the (n,) normalised weights of each particle

In equations

Example

For example, assuming we observe 32x32 images and want to track a moving circle. Assume the internal state we are estimating is the 4D vector (x, y, dx, dy), with 200 particles

        from pfilter import ParticleFilter, gaussian_noise, squared_error, independent_sample
        columns = ["x", "y", "radius", "dx", "dy"]
        from scipy.stats import norm, gamma, uniform 
        
        # prior sampling function for each variable
        # (assumes x and y are coordinates in the range 0-32)    
        prior_fn = independent_sample([uniform(loc=0, scale=32).rvs, 
                    uniform(loc=0, scale=32).rvs, 
                    gamma(a=2,loc=0,scale=10).rvs,
                    norm(loc=0, scale=0.5).rvs,
                    norm(loc=0, scale=0.5).rvs])
                                    
        # very simple linear dynamics: x += dx
        def velocity(x):
            xp = np.array(x)
            xp[0:2] += xp[3:5]        
        return xp
        
        # create the filter
        pf = pfilter.ParticleFilter(
                        prior_fn=prior_fn, 
                        observe_fn=blob,
                        n_particles=200,
                        dynamics_fn=velocity,
                        noise_fn=lambda x: 
                                    gaussian_noise(x, sigmas=[0.2, 0.2, 0.1, 0.05, 0.05]),
                        weight_fn=lambda x,y:squared_error(x, y, sigma=2),
                        resample_proportion=0.1,
                        column_names = columns)
                        
        # assuming image of the same dimensions/type as blob will produce
        pf.update(image) 
  • blob (200, 4) -> (200, 1024) which draws a blob on an image of size 32x32 (1024 pixels) for each internal state, our observation function
  • velocity (200, 4) -> (200, 4), our dynamics function, which just applies a single Euler step integrating the velocity
  • prior_fn which generates a (200,4) initial random state
  • gaussian_noise (200, 4) -> (200,4) which adds noise to the internal state
  • squared_error ((200,1024), (1024,)) -> (200,) the similarity measurement

See the notebook examples/example_filter.py for a working example using skimage and OpenCV which tracks a moving white circle.


pfilter's People

Contributors

0xflotus avatar a-mitani avatar dkloving avatar johannesbuchner avatar johnhw avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

pfilter's Issues

weights don't correspond to particles

While debugging my filter, I plotted my particles with color corresponding to their weights. As the weights seemed to be fairly random, I spent forever looking in various wrong directions before I realized that because of the order of operations, pf.weights corresponds to neither pf.original_particles or pf.particles (where pf is a ParticleFilter object). I think this is a bug because the weights are still used even after resampling to calculate mean states, etc. I fixed this by reshuffling the weights with the particles in the resampling set (line 280):

    # resampling (systematic resampling) step                                                                         
    if self.n_eff < self.n_eff_threshold:                                                                             
        indices = resample(self.weights)                                                                              
        self.particles = self.particles[indices, :]                                                                   
        self.weights = self.weights[indices] 

Improper configuration of modules

The __init__.py file in pfilter directory requires imports statement with "." preceeding filenames. without which user will encounter ModuleNotFoundError

Solution:

from pfilter import *
should be changed to from .pfilter import *

Reference:
https://github.com/numpy/numpy/blob/dea85807c258ded3f75528cce2a444468de93bc1/numpy/__init__.py#L112

https://github.com/tartley/colorama/blob/d69f83f53c0c5aa1081d1f5eebb2dc2df6028f37/colorama/__init__.py#L2

Add numpy dependency to setup.py

Hello, I'm looking forward to trying this package!

Just noting that it would be nice to add
install_requires=['numpy'],
into setup.py, so that when users run pip install pfilter, numpy is installed as well.

This was also mentioned in #3

Example request to learn how to use the library

Could you make me an example of a case of a rectilinear motion uniformly accelerated?

I would like to know how you would do it in order to learn and see how to scale it to more dimensions, etc.

Thanks!

Error using examples/timeseries.ipynb

When I try to run the timeseries.ipynb notebook, I get the following error:

(test) MacBook:examples upc$ ipython timeseries.ipynb

--------------------------------------------------------------------------- AxisError Traceback (most recent call last) ~/workspace/Docencia/comvis/repos/pfilter/examples/timeseries.ipynb in 14 resample_proportion=0.01) 15 ---> 16 filter_plot(x, y, yn, pf)

~/workspace/Docencia/comvis/repos/pfilter/examples/timeseries.ipynb in filter_plot(x, y, yn, pf, inputs)
31 def filter_plot(x, y, yn, pf, inputs=None):
32 """Apply a filter to yn, and plot the results using plot_particles()"""
---> 33 states = apply_filter(pf, yn, inputs)
34 plot_particles(x, y, yn, states)

~/workspace/Docencia/comvis/repos/pfilter/examples/timeseries.ipynb in apply_filter(pf, ys, inputs)
9 for i,y in enumerate(ys):
10 if inputs is None:
---> 11 pf.update(y)
12 else:
13 pf.update(y, **inputs[i])

~/venv/test/lib/python3.7/site-packages/pfilter/pfilter.py in update(self, observed)
196 # force to be positive
197 weights = np.clip(
--> 198 np.array(self.weight_fn(self.hypotheses, observed)), 0, np.inf
199 )
200 else:

~/workspace/Docencia/comvis/repos/pfilter/examples/timeseries.ipynb in (x, y)
11 n_particles=250,
12 noise_fn = lambda x: x + np.random.normal(0, noise, x.shape),
---> 13 weight_fn = lambda x,y : squared_error(x, y, sigma=sigma),
14 resample_proportion=0.01)
15

~/venv/test/lib/python3.7/site-packages/pfilter/pfilter.py in squared_error(x, y, sigma)
26 def squared_error(x, y, sigma=1):
27 # RBF kernel
---> 28 d = np.sum((x - y) ** 2, axis=(1, 2))
29 return np.exp(-d / (2.0 * sigma ** 2))
30

<array_function internals> in sum(*args, **kwargs)

~/venv/test/lib/python3.7/site-packages/numpy/core/fromnumeric.py in sum(a, axis, dtype, out, keepdims, initial, where)
2240
2241 return _wrapreduction(a, np.add, 'sum', axis, dtype, out, keepdims=keepdims,
-> 2242 initial=initial, where=where)
2243
2244

~/venv/test/lib/python3.7/site-packages/numpy/core/fromnumeric.py in _wrapreduction(obj, ufunc, method, axis, dtype, out, **kwargs)
85 return reduction(axis=axis, out=out, **passkwargs)
86
---> 87 return ufunc.reduce(obj, axis, dtype, out, **passkwargs)
88
89

AxisError: axis 2 is out of bounds for array of dimension 2

I have been looking in your code and it may be that the code in the github repo is not the same as the code of the package in pypi. The function squared_error() seems different.
Can this be solved?
Thanks!

Weight_fn parameters

In the documentation in pfilter.py line 165, it says that weight_fn should take parameters "real" and "hypothesized" in that order. But when the function is called in line 240, the "self.hypotheses" attribute is the first argument. Shouldn't the self.hypotheses attribute naturally be called as the hypothesized argument?

Add info about pyPI package

This package is available in Python Package Index which greatly improves the usability of this package

  • Add a link and instruction to install package from index
  • Add numpy to package requirements so that dependency is satisfied by pip itself

ImportError

after pip install pfilter, when I run the example_filter.py the error occured.
error

Resampling algorithm

Can you tell what type of resampling is implemented in the code you used from the scipy cookbook? The code is pretty opaque to me. I was trying to compare it to these examples:
https://github.com/rlabbe/Kalman-and-Bayesian-Filters-in-Python/blob/master/12-Particle-Filters.ipynb and it does look very similar to a couple.

Also, I don't understand why a scipy code example would be using a list comprehension in place of cumsum. That can't be as fast, can it?

In any case, I think would make sense to have the option to supply a different re-sample function to the ParticleFilter class. (And/or include a few built in options to choose from).

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.