@@ -32,12 +32,20 @@ run_control:
3232slideshow:
3333 slide_type: '-'
3434---
35+ import warnings
36+
3537import arviz as az
3638import matplotlib.pyplot as plt
3739import numpy as np
3840import pymc as pm
3941import pytensor.tensor as pt
4042import scipy as sp
43+
44+ # Ignore UserWarnings
45+ warnings.filterwarnings("ignore", category=UserWarning)
46+
47+ RANDOM_SEED = 8927
48+ np.random.seed(RANDOM_SEED)
4149```
4250
4351``` {code-cell} ipython3
@@ -96,16 +104,19 @@ run_control:
96104slideshow:
97105 slide_type: subslide
98106---
99- plt.figure(figsize=(10, 3))
100- plt.plot(x_t[:30], "k", label="$x(t)$", alpha=0.5)
101- plt.plot(z_t[:30], "r", label="$z(t)$", alpha=0.5)
102- plt.title("Transient")
103- plt.legend()
104- plt.subplot(122)
105- plt.plot(x_t[30:], "k", label="$x(t)$", alpha=0.5)
106- plt.plot(z_t[30:], "r", label="$z(t)$", alpha=0.5)
107- plt.title("All time")
108- plt.legend();
107+ fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 3))
108+
109+ ax1.plot(x_t[:30], "k", label="$x(t)$", alpha=0.5)
110+ ax1.plot(z_t[:30], "r", label="$z(t)$", alpha=0.5)
111+ ax1.set_title("Transient")
112+ ax1.legend()
113+
114+ ax2.plot(x_t[30:], "k", label="$x(t)$", alpha=0.5)
115+ ax2.plot(z_t[30:], "r", label="$z(t)$", alpha=0.5)
116+ ax2.set_title("All time")
117+ ax2.legend()
118+
119+ plt.tight_layout()
109120```
110121
111122+++ {"button": false, "new_sheet": false, "run_control": {"read_only": false}}
@@ -123,7 +134,7 @@ new_sheet: false
123134run_control:
124135 read_only: false
125136---
126- def lin_sde(x, lam):
137+ def lin_sde(x, lam, s2 ):
127138 return lam * x, s2
128139```
129140
@@ -144,11 +155,12 @@ slideshow:
144155---
145156with pm.Model() as model:
146157 # uniform prior, but we know it must be negative
147- l = pm.Flat("l")
158+ l = pm.HalfCauchy("l", beta=1)
159+ s = pm.Uniform("s", 0.005, 0.5)
148160
149161 # "hidden states" following a linear SDE distribution
150162 # parametrized by time step (det. variable) and lam (random variable)
151- xh = pm.EulerMaruyama("xh", dt=dt, sde_fn=lin_sde, sde_pars=(l, ), shape=N)
163+ xh = pm.EulerMaruyama("xh", dt=dt, sde_fn=lin_sde, sde_pars=(-l, s**2 ), shape=N, initval=x_t )
152164
153165 # predicted observation
154166 zh = pm.Normal("zh", mu=xh, sigma=5e-3, observed=z_t)
@@ -166,7 +178,7 @@ run_control:
166178 read_only: false
167179---
168180with model:
169- trace = pm.sample()
181+ trace = pm.sample(nuts_sampler="nutpie", random_seed=RANDOM_SEED, target_accept=0.99 )
170182```
171183
172184+++ {"button": false, "new_sheet": false, "run_control": {"read_only": false}, "slideshow": {"slide_type": "subslide"}}
@@ -185,7 +197,7 @@ plt.plot(x_t, "r", label="$x(t)$")
185197plt.legend()
186198
187199plt.subplot(122)
188- plt.hist(az.extract(trace.posterior)["l"], 30, label=r"$\hat{\lambda}$", alpha=0.5)
200+ plt.hist(-1 * az.extract(trace.posterior)["l"], 30, label=r"$\hat{\lambda}$", alpha=0.5)
189201plt.axvline(lam, color="r", label=r"$\lambda$", alpha=0.5)
190202plt.legend();
191203```
@@ -218,119 +230,6 @@ plt.legend();
218230
219231+++ {"button": false, "new_sheet": false, "run_control": {"read_only": false}}
220232
221- Note that
222-
223- - inference also estimates the initial conditions
224- - the observed data $z(t)$ lies fully within the 95% interval of the PPC.
225- - there are many other ways of evaluating fit
226-
227- +++ {"button": false, "new_sheet": false, "run_control": {"read_only": false}, "slideshow": {"slide_type": "slide"}}
228-
229- ### Toy model 2
230-
231- As the next model, let's use a 2D deterministic oscillator,
232- \begin{align}
233- \dot{x} &= \tau (x - x^3/3 + y) \\
234- \dot{y} &= \frac{1}{\tau} (a - x)
235- \end{align}
236-
237- with noisy observation $z(t) = m x + (1 - m) y + N(0, 0.05)$.
238-
239- ``` {code-cell} ipython3
240- N, tau, a, m, s2 = 200, 3.0, 1.05, 0.2, 1e-1
241- xs, ys = [0.0], [1.0]
242- for i in range(N):
243- x, y = xs[-1], ys[-1]
244- dx = tau * (x - x**3.0 / 3.0 + y)
245- dy = (1.0 / tau) * (a - x)
246- xs.append(x + dt * dx + np.sqrt(dt) * s2 * np.random.randn())
247- ys.append(y + dt * dy + np.sqrt(dt) * s2 * np.random.randn())
248- xs, ys = np.array(xs), np.array(ys)
249- zs = m * xs + (1 - m) * ys + np.random.randn(xs.size) * 0.1
250-
251- plt.figure(figsize=(10, 2))
252- plt.plot(xs, label="$x(t)$")
253- plt.plot(ys, label="$y(t)$")
254- plt.plot(zs, label="$z(t)$")
255- plt.legend()
256- ```
257-
258- +++ {"button": false, "new_sheet": false, "run_control": {"read_only": false}, "slideshow": {"slide_type": "subslide"}}
259-
260- Now, estimate the hidden states $x(t)$ and $y(t)$, as well as parameters $\tau$, $a$ and $m$.
261-
262- As before, we rewrite our SDE as a function returned drift & diffusion coefficients:
263-
264- ``` {code-cell} ipython3
265- ---
266- button: false
267- new_sheet: false
268- run_control:
269- read_only: false
270- ---
271- def osc_sde(xy, tau, a):
272- x, y = xy[:, 0], xy[:, 1]
273- dx = tau * (x - x**3.0 / 3.0 + y)
274- dy = (1.0 / tau) * (a - x)
275- dxy = pt.stack([dx, dy], axis=0).T
276- return dxy, s2
277- ```
278-
279- +++ {"button": false, "new_sheet": false, "run_control": {"read_only": false}}
280-
281- As before, the Euler-Maruyama discretization of the SDE is written as a prediction of the state at step $i+1$ based on the state at step $i$.
282-
283- +++ {"button": false, "new_sheet": false, "run_control": {"read_only": false}, "slideshow": {"slide_type": "subslide"}}
284-
285- We can now write our statistical model as before, with uninformative priors on $\tau$, $a$ and $m$:
286-
287- ``` {code-cell} ipython3
288- ---
289- button: false
290- new_sheet: false
291- run_control:
292- read_only: false
293- ---
294- xys = np.c_[xs, ys]
295-
296- with pm.Model() as model:
297- tau_h = pm.Uniform("tau_h", lower=0.1, upper=5.0)
298- a_h = pm.Uniform("a_h", lower=0.5, upper=1.5)
299- m_h = pm.Uniform("m_h", lower=0.0, upper=1.0)
300- xy_h = pm.EulerMaruyama(
301- "xy_h", dt=dt, sde_fn=osc_sde, sde_pars=(tau_h, a_h), shape=xys.shape, initval=xys
302- )
303- zh = pm.Normal("zh", mu=m_h * xy_h[:, 0] + (1 - m_h) * xy_h[:, 1], sigma=0.1, observed=zs)
304- ```
305-
306- ``` {code-cell} ipython3
307- pm.__version__
308- ```
309-
310- ``` {code-cell} ipython3
311- ---
312- button: false
313- new_sheet: false
314- run_control:
315- read_only: false
316- ---
317- with model:
318- pm.sample_posterior_predictive(trace, extend_inferencedata=True)
319- ```
320-
321- ``` {code-cell} ipython3
322- plt.figure(figsize=(10, 3))
323- plt.plot(
324- trace.posterior_predictive.quantile((0.025, 0.975), dim=("chain", "draw"))["zh"].values.T,
325- "k",
326- label=r"$z_{95\% PP}(t)$",
327- )
328- plt.plot(z_t, "r", label="$z(t)$")
329- plt.legend();
330- ```
331-
332- +++ {"button": false, "new_sheet": false, "run_control": {"read_only": false}}
333-
334233Note that the initial conditions are also estimated, and that most of the observed data $z(t)$ lies within the 95% interval of the PPC.
335234
336235Another approach is to look at draws from the sampling distribution of the data relative to the observed data. This too shows a good fit across the range of observations -- the posterior predictive mean almost perfectly tracks the data.
@@ -350,6 +249,17 @@ az.plot_ppc(trace)
350249:filter: docname in docnames
351250:::
352251
252+ ## Authors
253+ - Authored by @maedoc in July 2016
254+ - Updated to PyMC v5 by @fonnesbeck in September 2024
255+
256+ +++
257+
258+ ## References
259+ :::{bibliography}
260+ :filter: docname in docnames
261+ :::
262+
353263``` {code-cell} ipython3
354264%load_ext watermark
355265%watermark -n -u -v -iv -w
0 commit comments