Skip to content

Commit 364d361

Browse files
authored
Split up 'advanced usage' page (#522)
* Split up 'advanced usage' page * Use progress kwarg * Forcing samplers
1 parent 20209fe commit 364d361

File tree

8 files changed

+282
-247
lines changed

8 files changed

+282
-247
lines changed

_quarto.yml

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,15 +59,15 @@ website:
5959
- section: "Usage Tips"
6060
collapse-level: 1
6161
contents:
62-
- text: "Mode Estimation"
63-
href: tutorials/docs-17-mode-estimation/index.qmd
64-
- tutorials/docs-09-using-turing-advanced/index.qmd
6562
- tutorials/docs-10-using-turing-autodiff/index.qmd
63+
- tutorials/usage-custom-distribution/index.qmd
64+
- tutorials/usage-modifying-logprob/index.qmd
65+
- tutorials/usage-generated-quantities/index.qmd
66+
- tutorials/docs-17-mode-estimation/index.qmd
6667
- tutorials/docs-13-using-turing-performance-tips/index.qmd
67-
- tutorials/docs-11-using-turing-dynamichmc/index.qmd
6868
- tutorials/docs-15-using-turing-sampler-viz/index.qmd
69-
- text: "External Samplers"
70-
href: tutorials/docs-16-using-turing-external-samplers/index.qmd
69+
- tutorials/docs-11-using-turing-dynamichmc/index.qmd
70+
- tutorials/docs-16-using-turing-external-samplers/index.qmd
7171

7272
- section: "Tutorials"
7373
contents:
@@ -107,6 +107,7 @@ website:
107107
- section: "DynamicPPL in Depth"
108108
collapse-level: 1
109109
contents:
110+
- tutorials/dev-model-manual/index.qmd
110111
- tutorials/docs-05-for-developers-compiler/index.qmd
111112
- text: "A Mini Turing Implementation I: Compiler"
112113
href: tutorials/14-minituring/index.qmd

tutorials/dev-model-manual/index.qmd

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
---
2+
title: Manually Defining a Model
3+
engine: julia
4+
---
5+
6+
Traditionally, models in Turing are defined using the `@model` macro:
7+
8+
```{julia}
9+
using Turing
10+
11+
@model function gdemo(x)
12+
# Set priors.
13+
s² ~ InverseGamma(2, 3)
14+
m ~ Normal(0, sqrt(s²))
15+
16+
# Observe each value of x.
17+
@. x ~ Normal(m, sqrt(s²))
18+
end
19+
20+
model = gdemo([1.5, 2.0])
21+
```
22+
23+
The `@model` macro accepts a function definition and rewrites it such that call of the function generates a `Model` struct for use by the sampler.
24+
25+
However, models can be constructed by hand without the use of a macro.
26+
Taking the `gdemo` model above as an example, the macro-based definition can be implemented also (a bit less generally) with the macro-free version
27+
28+
```{julia}
29+
# Create the model function.
30+
function gdemo2(model, varinfo, context, x)
31+
# Assume s² has an InverseGamma distribution.
32+
s², varinfo = DynamicPPL.tilde_assume!!(
33+
context, InverseGamma(2, 3), Turing.@varname(s²), varinfo
34+
)
35+
36+
# Assume m has a Normal distribution.
37+
m, varinfo = DynamicPPL.tilde_assume!!(
38+
context, Normal(0, sqrt(s²)), Turing.@varname(m), varinfo
39+
)
40+
41+
# Observe each value of x[i] according to a Normal distribution.
42+
return DynamicPPL.dot_tilde_observe!!(
43+
context, Normal(m, sqrt(s²)), x, Turing.@varname(x), varinfo
44+
)
45+
end
46+
gdemo2(x) = Turing.Model(gdemo2, (; x))
47+
48+
# Instantiate a Model object with our data variables.
49+
model2 = gdemo2([1.5, 2.0])
50+
```
51+
52+
We can sample from this model in the same way:
53+
54+
```{julia}
55+
chain = sample(model2, NUTS(), 1000; progress=false)
56+
```
57+
58+
The subsequent pages in this section will show how the `@model` macro does this behind-the-scenes.

tutorials/docs-09-using-turing-advanced/index.qmd

Lines changed: 5 additions & 238 deletions
Original file line numberDiff line numberDiff line change
@@ -3,242 +3,9 @@ title: Advanced Usage
33
engine: julia
44
---
55

6-
```{julia}
7-
#| echo: false
8-
#| output: false
9-
using Pkg;
10-
Pkg.instantiate();
11-
```
6+
This page has been separated into new sections. Please update any bookmarks you might have:
127

13-
```{julia}
14-
#| echo: false
15-
using Distributions, Turing, Random, Bijectors
16-
```
17-
18-
## How to Define a Customized Distribution
19-
20-
`Turing.jl` supports the use of distributions from the Distributions.jl package. By extension, it also supports the use of customized distributions by defining them as subtypes of `Distribution` type of the Distributions.jl package, as well as corresponding functions.
21-
22-
Below shows a workflow of how to define a customized distribution, using our own implementation of a simple `Uniform` distribution as a simple example.
23-
24-
### 1. Define the Distribution Type
25-
26-
First, define a type of the distribution, as a subtype of a corresponding distribution type in the Distributions.jl package.
27-
28-
```{julia}
29-
struct CustomUniform <: ContinuousUnivariateDistribution end
30-
```
31-
32-
### 2. Implement Sampling and Evaluation of the log-pdf
33-
34-
Second, define `rand` and `logpdf`, which will be used to run the model.
35-
36-
```{julia}
37-
# sample in [0, 1]
38-
Distributions.rand(rng::AbstractRNG, d::CustomUniform) = rand(rng)
39-
40-
# p(x) = 1 → logp(x) = 0
41-
Distributions.logpdf(d::CustomUniform, x::Real) = zero(x)
42-
```
43-
44-
### 3. Define Helper Functions
45-
46-
In most cases, it may be required to define some helper functions.
47-
48-
#### 3.1 Domain Transformation
49-
50-
Certain samplers, such as `HMC`, require the domain of the priors to be unbounded. Therefore, to use our `CustomUniform` as a prior in a model we also need to define how to transform samples from `[0, 1]` to ``. To do this, we simply need to define the corresponding `Bijector` from `Bijectors.jl`, which is what `Turing.jl` uses internally to deal with constrained distributions.
51-
52-
To transform from `[0, 1]` to `` we can use the `Logit` bijector:
53-
54-
```{julia}
55-
Bijectors.bijector(d::CustomUniform) = Logit(0.0, 1.0)
56-
```
57-
58-
You'd do the exact same thing for `ContinuousMultivariateDistribution` and `ContinuousMatrixDistribution`. For example, `Wishart` defines a distribution over positive-definite matrices and so `bijector` returns a `PDBijector` when called with a `Wishart` distribution as an argument. For discrete distributions, there is no need to define a bijector; the `Identity` bijector is used by default.
59-
60-
Alternatively, for `UnivariateDistribution` we can define the `minimum` and `maximum` of the distribution
61-
62-
```{julia}
63-
Distributions.minimum(d::CustomUniform) = 0.0
64-
Distributions.maximum(d::CustomUniform) = 1.0
65-
```
66-
67-
and `Bijectors.jl` will return a default `Bijector` called `TruncatedBijector` which makes use of `minimum` and `maximum` derive the correct transformation.
68-
69-
Internally, Turing basically does the following when it needs to convert a constrained distribution to an unconstrained distribution, e.g. when sampling using `HMC`:
70-
71-
```{julia}
72-
dist = Gamma(2,3)
73-
b = bijector(dist)
74-
transformed_dist = transformed(dist, b) # results in distribution with transformed support + correction for logpdf
75-
```
76-
77-
and then we can call `rand` and `logpdf` as usual, where
78-
79-
- `rand(transformed_dist)` returns a sample in the unconstrained space, and
80-
- `logpdf(transformed_dist, y)` returns the log density of the original distribution, but with `y` living in the unconstrained space.
81-
82-
To read more about Bijectors.jl, check out [the project README](https://github.com/TuringLang/Bijectors.jl).
83-
84-
## Update the accumulated log probability in the model definition
85-
86-
Turing accumulates log probabilities internally in an internal data structure that is accessible through
87-
the internal variable `__varinfo__` inside of the model definition (see below for more details about model internals).
88-
However, since users should not have to deal with internal data structures, a macro `Turing.@addlogprob!` is provided
89-
that increases the accumulated log probability. For instance, this allows you to
90-
[include arbitrary terms in the likelihood](https://github.com/TuringLang/Turing.jl/issues/1332)
91-
92-
```{julia}
93-
using Turing
94-
95-
myloglikelihood(x, μ) = loglikelihood(Normal(μ, 1), x)
96-
97-
@model function demo(x)
98-
μ ~ Normal()
99-
Turing.@addlogprob! myloglikelihood(x, μ)
100-
end
101-
```
102-
103-
and to [reject samples](https://github.com/TuringLang/Turing.jl/issues/1328):
104-
105-
```{julia}
106-
using Turing
107-
using LinearAlgebra
108-
109-
@model function demo(x)
110-
m ~ MvNormal(zero(x), I)
111-
if dot(m, x) < 0
112-
Turing.@addlogprob! -Inf
113-
# Exit the model evaluation early
114-
return nothing
115-
end
116-
117-
x ~ MvNormal(m, I)
118-
return nothing
119-
end
120-
```
121-
122-
Note that `@addlogprob!` always increases the accumulated log probability, regardless of the provided
123-
sampling context. For instance, if you do not want to apply `Turing.@addlogprob!` when evaluating the
124-
prior of your model but only when computing the log likelihood and the log joint probability, then you
125-
should [check the type of the internal variable `__context_`](https://github.com/TuringLang/DynamicPPL.jl/issues/154)
126-
such as
127-
128-
```{julia}
129-
#| eval: false
130-
if DynamicPPL.leafcontext(__context__) !== Turing.PriorContext()
131-
Turing.@addlogprob! myloglikelihood(x, μ)
132-
end
133-
```
134-
135-
## Model Internals
136-
137-
The `@model` macro accepts a function definition and rewrites it such that call of the function generates a `Model` struct for use by the sampler.
138-
Models can be constructed by hand without the use of a macro.
139-
Taking the `gdemo` model as an example, the macro-based definition
140-
141-
```{julia}
142-
using Turing
143-
144-
@model function gdemo(x)
145-
# Set priors.
146-
s² ~ InverseGamma(2, 3)
147-
m ~ Normal(0, sqrt(s²))
148-
149-
# Observe each value of x.
150-
@. x ~ Normal(m, sqrt(s²))
151-
end
152-
153-
model = gdemo([1.5, 2.0])
154-
```
155-
156-
can be implemented also (a bit less generally) with the macro-free version
157-
158-
```{julia}
159-
using Turing
160-
161-
# Create the model function.
162-
function gdemo(model, varinfo, context, x)
163-
# Assume s² has an InverseGamma distribution.
164-
s², varinfo = DynamicPPL.tilde_assume!!(
165-
context, InverseGamma(2, 3), Turing.@varname(s²), varinfo
166-
)
167-
168-
# Assume m has a Normal distribution.
169-
m, varinfo = DynamicPPL.tilde_assume!!(
170-
context, Normal(0, sqrt(s²)), Turing.@varname(m), varinfo
171-
)
172-
173-
# Observe each value of x[i] according to a Normal distribution.
174-
return DynamicPPL.dot_tilde_observe!!(
175-
context, Normal(m, sqrt(s²)), x, Turing.@varname(x), varinfo
176-
)
177-
end
178-
gdemo(x) = Turing.Model(gdemo, (; x))
179-
180-
# Instantiate a Model object with our data variables.
181-
model = gdemo([1.5, 2.0])
182-
```
183-
184-
### Reparametrization and generated_quantities
185-
186-
Often, the most natural parameterization for a model is not the most computationally feasible. Consider the following
187-
(efficiently reparametrized) implementation of Neal's funnel [(Neal, 2003)](https://arxiv.org/abs/physics/0009028):
188-
189-
```{julia}
190-
#| eval: false
191-
@model function Neal()
192-
# Raw draws
193-
y_raw ~ Normal(0, 1)
194-
x_raw ~ arraydist([Normal(0, 1) for i in 1:9])
195-
196-
# Transform:
197-
y = 3 * y_raw
198-
x = exp.(y ./ 2) .* x_raw
199-
200-
# Return:
201-
return [x; y]
202-
end
203-
```
204-
205-
In this case, the random variables exposed in the chain (`x_raw`, `y_raw`) are not in a helpful form — what we're after is the deterministically transformed variables `x, y`.
206-
207-
More generally, there are often quantities in our models that we might be interested in viewing, but which are not explicitly present in our chain.
208-
209-
We can generate draws from these variables — in this case, `x, y` — by adding them as a return statement to the model, and then calling `generated_quantities(model, chain)`. Calling this function outputs an array of values specified in the return statement of the model.
210-
211-
For example, in the above reparametrization, we sample from our model:
212-
213-
```{julia}
214-
#| eval: false
215-
chain = sample(Neal(), NUTS(), 1000)
216-
```
217-
218-
and then call:
219-
220-
```{julia}
221-
#| eval: false
222-
generated_quantities(Neal(), chain)
223-
```
224-
225-
to return an array for each posterior sample containing `x1, x2, ... x9, y`.
226-
227-
In this case, it might be useful to reorganize our output into a matrix for plotting:
228-
229-
```{julia}
230-
#| eval: false
231-
reparam_chain = reduce(hcat, generated_quantities(Neal(), chain))'
232-
```
233-
234-
Where we can recover a vector of our samples as follows:
235-
236-
```{julia}
237-
#| eval: false
238-
x1_samples = reparam_chain[:, 1]
239-
y_samples = reparam_chain[:, 10]
240-
```
241-
242-
## Task Copying
243-
244-
Turing [copies](https://github.com/JuliaLang/julia/issues/4085) Julia tasks to deliver efficient inference algorithms, but it also provides alternative slower implementation as a fallback. Task copying is enabled by default. Task copying requires us to use the `TapedTask` facility which is provided by [Libtask](https://github.com/TuringLang/Libtask.jl) to create tasks.
8+
- [Custom Distributions](../../tutorials/usage-custom-distribution)
9+
- [Modifying the Log Probability](../../tutorials/usage-modifying-logprob/)
10+
- [Defining a Model without `@model`](../../tutorials/dev-model-manual/)
11+
- [Reparametrization and Generated Quantities](../../tutorials/usage-generated-quantities/)

tutorials/docs-15-using-turing-sampler-viz/index.qmd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,4 +194,4 @@ Next, we plot using 50 particles.
194194
```{julia}
195195
c = sample(model, PG(50), 1000)
196196
plot_sampler(c)
197-
```
197+
```

tutorials/docs-16-using-turing-external-samplers/index.qmd

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
title: Using External Sampler
2+
title: Using External Samplers
33
engine: julia
44
---
55

@@ -176,4 +176,4 @@ For practical examples of how to adapt a sampling library to the `AbstractMCMC`
176176
[^1]: Xu et al., [AdvancedHMC.jl: A robust, modular and efficient implementation of advanced HMC algorithms](http://proceedings.mlr.press/v118/xu20a/xu20a.pdf), 2019
177177
[^2]: Zhang et al., [Pathfinder: Parallel quasi-Newton variational inference](https://arxiv.org/abs/2108.03782), 2021
178178
[^3]: Robnik et al, [Microcanonical Hamiltonian Monte Carlo](https://arxiv.org/abs/2212.08549), 2022
179-
[^4]: Robnik and Seljak, [Langevine Hamiltonian Monte Carlo](https://arxiv.org/abs/2303.18221), 2023
179+
[^4]: Robnik and Seljak, [Langevine Hamiltonian Monte Carlo](https://arxiv.org/abs/2303.18221), 2023

0 commit comments

Comments
 (0)