tensorflow / tensorflow/probability
Add Poisson quantile
Open
Nobody has claimed this yet.
- Dominant language
- Jupyter Notebook
- Stars
- 4.4k
- Forks
- 1.1k
- PR merge metrics
- No merged PRs in 30d
Description
Adding the poisson quantile would be useful. As a use case, JAXNS uses quantiles to reparametrise. Below is a bisection approach accurate to rate of 1e4.
from functools import partial
import jax
import numpy as np
import pytest
from jax import numpy as jnp, vmap, lax
from jaxns.internals.types import int_type
def _poisson_quantile_bisection(U, rate, max_iter=15, unroll: bool = True):
"""
Compute the quantile of the Poisson distribution using bisection.
Args:
U: the base measure
rate: the rate of the Poisson distribution
max_iter: the maximum number of iterations.By default accurate up to rate=1e4.
unroll: whether to unroll the loop
Returns:
the quantile
"""
# max_iter is set so that error < 1 up to rate of 1e4
rate = jnp.maximum(jnp.asarray(rate), 1e-5)
if np.size(rate) > 1:
raise ValueError("Rate must be a scalar")
if np.size(U) > 1:
U_flat = U.ravel()
x_final, x_results = vmap(lambda u: _poisson_quantile_bisection(u, rate, max_iter, unroll))(U_flat)
return x_final.reshape(U.shape), x_results.reshape(U.shape + (max_iter,))
def smooth_cdf(x, rate):
return lax.igammac(x + 1., rate)
def fixed_point_update(x, args):
(a, b, f_a, f_b) = x
c = 0.5 * (a + b)
f_c = smooth_cdf(c, rate)
left = f_c > U
a1 = jnp.where(left, a, c)
f_a1 = jnp.where(left, f_a, f_c)
b1 = jnp.where(left, c, b)
f_b1 = jnp.where(left, f_c, f_b)
a2 = a
f_a2 = f_a
b2 = b * 2.
f_b2 = smooth_cdf(b2, rate)
bounded = f_b >= U # a already bounds.
a = jnp.where(bounded, a1, a2)
b = jnp.where(bounded, b1, b2)
f_a = jnp.where(bounded, f_a1, f_a2)
f_b = jnp.where(bounded, f_b1, f_b2)
new_x = (a, b, f_a, f_b)
return new_x, 0.5 * (a + b)
a = jnp.asarray(0.)
b = jnp.asarray(rate)
f_a = jnp.asarray(0.)
f_b = smooth_cdf(b, rate)
init = (a, b, f_a, f_b)
# Dummy array to facilitate using scan for a fixed number of iterations
(a, b, f_a, f_b), x_results = lax.scan(
fixed_point_update,
init,
jnp.arange(max_iter),
unroll=max_iter if unroll else 1
)
c = 0.5 * (a + b)
return c, x_results
@partial(jax.jit, static_argnames=("unroll",))
def _poisson_quantile(U, rate, unroll: bool = False):
"""
Compute the quantile of the Poisson distribution using bisection.
Args:
U: the base measure
rate: the rate of the Poisson distribution
unroll: whether to unroll the loop
Returns:
the quantile
"""
x, _ = _poisson_quantile_bisection(U, rate, unroll=unroll)
return x.astype(int_type)
@pytest.mark.parametrize("rate, error", (
[2.0, 1.],
[10., 1.],
[100., 1.],
[1000., 1.],
[10000., 1.]
)
)
def test_poisson_quantile_bisection(rate, error):
U = jnp.linspace(0., 1. - np.spacing(1.), 1000)
x, x_results = _poisson_quantile_bisection(U, rate, unroll=False)
diff_last_two = jnp.abs(x_results[..., -1] - x_results[..., -2])
# Make sure less than 1 apart
assert jnp.all(diff_last_two <= error)
@pytest.mark.parametrize("rate", [2.0, 10., 100., 1000., 10000.])
def test_poisson_quantile(rate):
U = jnp.linspace(0., 1. - np.spacing(1.), 10000)
x = _poisson_quantile(U, rate)
assert jnp.all(jnp.isfinite(x))
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
The issue names no target file or existing API, but provides a JAX/Python bisection prototype and pytest checks. Start by locating the repository's distribution implementations and quantile APIs, then determine where Poisson quantile support belongs. Done means the feature is integrated with matching accuracy and finite-value tests for the listed rates.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- machine-learning
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 30/100