You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: usage/submodels/index.qmd
+35-18Lines changed: 35 additions & 18 deletions
Original file line number
Diff line number
Diff line change
@@ -142,7 +142,6 @@ end
142
142
rand(Xoshiro(468), outer_with_varinfo())
143
143
```
144
144
145
-
146
145
## Example: linear models
147
146
148
147
Here is a motivating example for the use of submodels.
@@ -159,67 +158,83 @@ where $d$ is _some_ distribution parameterised by the value of $\mu$, which we d
159
158
160
159
In practice, what we would do is to write several different models, one for each function $f$:
161
160
162
-
```julia
161
+
```{julia}
163
162
@model function normal(x, y)
164
163
c0 ~ Normal(0, 5)
165
164
c1 ~ Normal(0, 5)
166
-
mu = c0 + c1 * x
165
+
mu = c0 .+ c1 .* x
167
166
# Assume that y = mu, and that the noise in `y` is
168
167
# normally distributed with standard deviation sigma
169
168
sigma ~ truncated(Cauchy(0, 3); lower=0)
170
-
y ~Normal(mu, sigma)
169
+
for i in eachindex(y)
170
+
y[i] ~ Normal(mu[i], sigma)
171
+
end
171
172
end
172
173
173
174
@model function logpoisson(x, y)
174
175
c0 ~ Normal(0, 5)
175
176
c1 ~ Normal(0, 5)
176
-
mu = c0 + c1 * x
177
+
mu = c0 .+ c1 .* x
177
178
# exponentiate mu because the rate parameter of
178
179
# a Poisson distribution must be positive
179
-
y ~Poisson(exp(mu))
180
+
for i in eachindex(y)
181
+
y[i] ~ Poisson(exp(mu[i]))
182
+
end
180
183
end
181
184
182
185
# and so on...
183
186
```
184
187
188
+
::: {.callout-note}
189
+
You could use `arraydist` to avoid the loops: for example, in `logpoisson`, one could write `y ~ arraydist(Poisson.(exp.(mu)))`, but for simplicity in this tutorial we spell it out fully.
190
+
:::
191
+
185
192
We would then fit all of our models and use some criterion to test which model is most suitable (see e.g. [Wikipedia](https://en.wikipedia.org/wiki/Model_selection), or section 3.4 of Bishop's *Pattern Recognition and Machine Learning*).
186
193
187
194
However, the code above is quite repetitive.
188
195
For example, if we wanted to adjust the priors on `c0` and `c1`, we would have to do it in each model separately.
189
196
If this was any other kind of code, we would naturally think of extracting the common parts into a separate function.
190
197
In this case we can do exactly that with a submodel:
191
198
192
-
```julia
199
+
```{julia}
193
200
@model function priors(x)
194
201
c0 ~ Normal(0, 5)
195
202
c1 ~ Normal(0, 5)
196
-
mu = c0 + c1 * x
203
+
mu = c0 .+ c1 .* x
197
204
return (; c0=c0, c1=c1, mu=mu)
198
205
end
199
206
200
207
@model function normal(x, y)
201
-
(; c0, c1, mu)=to_submodel(priors(x))
208
+
ps = to_submodel(priors(x))
202
209
sigma ~ truncated(Cauchy(0, 3); lower=0)
203
-
y ~Normal(mu, sigma)
210
+
for i in eachindex(y)
211
+
y[i] ~ Normal(ps.mu[i], sigma)
212
+
end
204
213
end
205
214
206
215
@model function logpoisson(x, y)
207
-
(; c0, c1, mu) =to_submodel(priors(x))
208
-
y ~Poisson(exp(mu))
216
+
ps = to_submodel(priors(x))
217
+
for i in eachindex(y)
218
+
y[i] ~ Poisson(exp(ps.mu[i]))
219
+
end
209
220
end
210
221
```
211
222
212
223
One could go even further and extract the `y` section into its own submodel as well, which would bring us to a generalised linear modelling interface that does not actually require the user to define their own Turing models at all:
213
224
214
-
```julia
225
+
```{julia}
215
226
@model function normal_family(mu, y)
216
227
sigma ~ truncated(Cauchy(0, 3); lower=0)
217
-
y ~Normal(mu, sigma)
228
+
for i in eachindex(y)
229
+
y[i] ~ Normal(mu[i], sigma)
230
+
end
218
231
return nothing
219
232
end
220
233
221
234
@model function logpoisson_family(mu, y)
222
-
y ~Poisson(exp(mu))
235
+
for i in eachindex(y)
236
+
y[i] ~ Poisson(exp(mu[i]))
237
+
end
223
238
return nothing
224
239
end
225
240
@@ -236,16 +251,18 @@ function make_model(x, y, family::Symbol)
While this final example really showcases the composability of submodels, it also illustrates a minor syntactic drawback.
247
264
In the case of the `family_model`, we do not care about its return value because it is not used anywhere else in the model.
248
-
Ideally, we would therefore not need to place anything on the left-hand side of `to_submodel`.
265
+
Ideally, we should therefore not need to place anything on the left-hand side of `to_submodel`.
249
266
However, because the special behaviour of `to_submodel` relies on the tilde operator, and the tilde operator requires a left-hand side, we have to use a dummy variable (here `_n`).
0 commit comments