@@ -32,12 +32,20 @@ run_control:
32
32
slideshow:
33
33
slide_type: '-'
34
34
---
35
+ import warnings
36
+
35
37
import arviz as az
36
38
import matplotlib.pyplot as plt
37
39
import numpy as np
38
40
import pymc as pm
39
41
import pytensor.tensor as pt
40
42
import scipy as sp
43
+
44
+ # Ignore UserWarnings
45
+ warnings.filterwarnings("ignore", category=UserWarning)
46
+
47
+ RANDOM_SEED = 8927
48
+ np.random.seed(RANDOM_SEED)
41
49
```
42
50
43
51
``` {code-cell} ipython3
@@ -96,16 +104,19 @@ run_control:
96
104
slideshow:
97
105
slide_type: subslide
98
106
---
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()
109
120
```
110
121
111
122
+++ {"button": false, "new_sheet": false, "run_control": {"read_only": false}}
@@ -123,7 +134,7 @@ new_sheet: false
123
134
run_control:
124
135
read_only: false
125
136
---
126
- def lin_sde(x, lam):
137
+ def lin_sde(x, lam, s2 ):
127
138
return lam * x, s2
128
139
```
129
140
@@ -144,11 +155,12 @@ slideshow:
144
155
---
145
156
with pm.Model() as model:
146
157
# 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)
148
160
149
161
# "hidden states" following a linear SDE distribution
150
162
# 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 )
152
164
153
165
# predicted observation
154
166
zh = pm.Normal("zh", mu=xh, sigma=5e-3, observed=z_t)
@@ -166,7 +178,7 @@ run_control:
166
178
read_only: false
167
179
---
168
180
with model:
169
- trace = pm.sample()
181
+ trace = pm.sample(nuts_sampler="nutpie", random_seed=RANDOM_SEED, target_accept=0.99 )
170
182
```
171
183
172
184
+++ {"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)$")
185
197
plt.legend()
186
198
187
199
plt.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)
189
201
plt.axvline(lam, color="r", label=r"$\lambda$", alpha=0.5)
190
202
plt.legend();
191
203
```
@@ -218,119 +230,6 @@ plt.legend();
218
230
219
231
+++ {"button": false, "new_sheet": false, "run_control": {"read_only": false}}
220
232
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
-
334
233
Note that the initial conditions are also estimated, and that most of the observed data $z(t)$ lies within the 95% interval of the PPC.
335
234
336
235
Another 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.
0 commit comments