Skip to content

Commit 3ab09db

Browse files
authored
overhaul docs and export some things that should be exported (#426)
* overhaul docs * bump patch * document orderd * readme * fix piracy * readme html * don't html
1 parent f466dab commit 3ab09db

File tree

17 files changed

+594
-369
lines changed

17 files changed

+594
-369
lines changed

.JuliaFormatter.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
style="blue"
2-
format_markdown=true
2+
format_markdown=true

HISTORY.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
# 0.15.13
2+
3+
Exports extra functionality that should probably have been exported, namely `ordered`, `isinvertible`, and `columnwise`, from Bijectors.jl
4+
5+
The docs have been thoroughly restructured.
6+
17
# 0.15.12
28

39
Improved implementation of the Enzyme rule for `Bijectors.find_alpha`.

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "Bijectors"
22
uuid = "76274a88-744f-5084-9051-94815aaf08c4"
3-
version = "0.15.12"
3+
version = "0.15.13"
44

55
[deps]
66
ArgCheck = "dce04be8-c92d-5529-be00-80e4d2c0e197"

README.md

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,35 @@
11
# Bijectors.jl
22

3-
[![Docs - Stable](https://img.shields.io/badge/docs-stable-blue.svg)](https://turinglang.github.io/Bijectors.jl/stable)
4-
[![Docs - Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://turinglang.github.io/Bijectors.jl/dev)
5-
[![Interface tests](https://github.com/TuringLang/Bijectors.jl/workflows/Interface%20tests/badge.svg?branch=main)](https://github.com/TuringLang/Bijectors.jl/actions?query=workflow%3A%22Interface+tests%22+branch%3Amain)
6-
[![AD tests](https://github.com/TuringLang/Bijectors.jl/workflows/AD%20tests/badge.svg?branch=main)](https://github.com/TuringLang/Bijectors.jl/actions?query=workflow%3A%22AD+tests%22+branch%3Amain)
3+
[![Documentation for latest stable release](https://img.shields.io/badge/docs-stable-blue.svg)](https://turinglang.github.io/Bijectors.jl)
4+
[![Documentation for development version](https://img.shields.io/badge/docs-dev-blue.svg)](https://turinglang.github.io/Bijectors.jl/dev)
5+
[![CI](https://github.com/TuringLang/Bijectors.jl/actions/workflows/CI.yml/badge.svg)](https://github.com/TuringLang/Bijectors.jl/actions/workflows/CI.yml)
76

8-
*A package for transforming distributions, used by [Turing.jl](https://github.com/TuringLang/Turing.jl).*
7+
Bijectors.jl implements functions for transforming random variables and probability distributions.
98

10-
Bijectors.jl implements both an interface for transforming distributions from Distributions.jl and many transformations needed in this context.
11-
This package is used heavily in the probabilistic programming language Turing.jl.
9+
A quick overview of some of the key functionality is provided below:
1210

13-
See the [documentation](https://turinglang.github.io/Bijectors.jl) for more.
11+
```julia
12+
julia> using Bijectors;
13+
dist = LogNormal();
14+
LogNormal{Float64}=0.0, σ=1.0)
1415

15-
## Do you want to contribute?
16+
julia> x = rand(dist) # Constrained to (0, ∞)
17+
0.6471106974390148
1618

17-
If you feel you have some relevant skills and are interested in contributing, please get in touch!
18-
You can find us in the #turing channel on the [Julia Slack](https://julialang.org/slack/) or [Discourse](https://discourse.julialang.org).
19-
If you're having any problems, please open a Github issue, even if the problem seems small (like help figuring out an error message).
20-
Every issue you open helps us to improve the library!
19+
julia> b = bijector(dist) # This maps from (0, ∞) to ℝ
20+
(::Base.Fix1{typeof(broadcast), typeof(log)}) (generic function with 1 method)
21+
22+
julia> y = b(x) # Unconstrained value in ℝ
23+
-0.43523790570180304
24+
25+
julia> # Log-absolute determinant of the Jacobian at x.
26+
with_logabsdet_jacobian(b, x)
27+
(-0.43523790570180304, 0.43523790570180304)
28+
```
29+
30+
Please see the [documentation](https://turinglang.github.io/Bijectors.jl) for more information.
31+
32+
## Get in touch
33+
34+
If you have any questions, please feel free to [post on Julia Slack](https://julialang.slack.com/archives/CCYDC34A0) or [Discourse](https://discourse.julialang.org/).
35+
We also very much welcome GitHub issues or pull requests!

docs/Project.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
[deps]
22
Bijectors = "76274a88-744f-5084-9051-94815aaf08c4"
33
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
4+
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
45
Functors = "d9f16b24-f501-4c13-a1f2-28368ffc5196"
56
StableRNGs = "860ef19b-820b-49d6-a774-d7a799459cd3"
67

docs/make.jl

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,13 @@ makedocs(;
99
format=Documenter.HTML(),
1010
modules=[Bijectors],
1111
pages=[
12-
"Home" => "index.md",
13-
"Transforms" => "transforms.md",
14-
"Distributions.jl integration" => "distributions.md",
15-
"Examples" => "examples.md",
12+
"index.md",
13+
"interface.md",
14+
"defining.md",
15+
"distributions.md",
16+
"types.md",
17+
"advi.md",
18+
"flows.md",
1619
],
1720
checkdocs=:exports,
1821
doctest=false,

docs/src/advi.md

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# Example: Variational inference
2+
3+
The real utility of `TransformedDistribution` becomes more apparent when using `transformed(dist, b)` for any bijector `b`.
4+
To get the transformed distribution corresponding to the `Beta(2, 2)`, we called `transformed(dist)` before.
5+
This is an alias for `transformed(dist, bijector(dist))`.
6+
Remember `bijector(dist)` returns the constrained-to-constrained bijector for that particular `Distribution`.
7+
But we can of course construct a `TransformedDistribution` using different bijectors with the same `dist`.
8+
9+
This is particularly useful in _Automatic Differentiation Variational Inference (ADVI)_.
10+
11+
## Univariate ADVI
12+
13+
An important part of ADVI is to approximate a constrained distribution, e.g. `Beta`, as follows:
14+
15+
1. Sample `x` from a `Normal` with parameters `μ` and `σ`, i.e. `x ~ Normal(μ, σ)`.
16+
2. Transform `x` to `y` s.t. `y ∈ support(Beta)`, with the transform being a differentiable bijection with a differentiable inverse (a "bijector").
17+
18+
This then defines a probability density with the same _support_ as `Beta`!
19+
Of course, it's unlikely that it will be the same density, but it's an _approximation_.
20+
21+
Creating such a distribution can be done with `Bijector` and `TransformedDistribution`:
22+
23+
```@example advi
24+
using Bijectors
25+
using StableRNGs: StableRNG
26+
rng = StableRNG(42)
27+
28+
dist = Beta(2, 2)
29+
b = bijector(dist) # (0, 1) → ℝ
30+
b⁻¹ = inverse(b) # ℝ → (0, 1)
31+
td = transformed(Normal(), b⁻¹) # x ∼ 𝓝(0, 1) then b(x) ∈ (0, 1)
32+
x = rand(rng, td) # ∈ (0, 1)
33+
```
34+
35+
It's worth noting that `support(Beta)` is the _closed_ interval `[0, 1]`, while the constrained-to-unconstrained bijection, `Logit` in this case, is only well-defined as a map `(0, 1) → ℝ` for the _open_ interval `(0, 1)`.
36+
This is of course not an implementation detail.
37+
`` is itself open, thus no continuous bijection exists from a _closed_ interval to ``.
38+
But since the boundaries of a closed interval has what's known as measure zero, this doesn't end up affecting the resulting density with support on the entire real line.
39+
In practice, this means that
40+
41+
```@example advi
42+
td = transformed(Beta())
43+
inverse(td.transform)(rand(rng, td))
44+
```
45+
46+
will never result in `0` or `1` though any sample arbitrarily close to either `0` or `1` is possible.
47+
_Disclaimer: numerical accuracy is limited, so you might still see `0` and `1` if you're 'lucky'._
48+
49+
## Multivariate ADVI example
50+
51+
We can also do _multivariate_ ADVI using the `Stacked` bijector.
52+
`Stacked` gives us a way to combine univariate and/or multivariate bijectors into a singe multivariate bijector.
53+
Say you have a vector `x` of length 2 and you want to transform the first entry using `Exp` and the second entry using `Log`.
54+
`Stacked` gives you an easy and efficient way of representing such a bijector.
55+
56+
```@example advi
57+
using Bijectors: SimplexBijector
58+
59+
# Original distributions
60+
dists = (Beta(), InverseGamma(), Dirichlet(2, 3))
61+
62+
# Construct the corresponding ranges
63+
function make_ranges(dists)
64+
ranges = []
65+
idx = 1
66+
for i in 1:length(dists)
67+
d = dists[i]
68+
push!(ranges, idx:(idx + length(d) - 1))
69+
idx += length(d)
70+
end
71+
return ranges
72+
end
73+
74+
ranges = make_ranges(dists)
75+
ranges
76+
```
77+
78+
```@example advi
79+
# Base distribution; mean-field normal
80+
num_params = ranges[end][end]
81+
82+
d = MvNormal(zeros(num_params), ones(num_params));
83+
84+
# Construct the transform
85+
bs = bijector.(dists) # constrained-to-unconstrained bijectors for dists
86+
ibs = inverse.(bs) # invert, so we get unconstrained-to-constrained
87+
sb = Stacked(ibs, ranges) # => Stacked <: Bijector
88+
89+
# Mean-field normal with unconstrained-to-constrained stacked bijector
90+
td = transformed(d, sb)
91+
y = rand(td)
92+
```
93+
94+
As can be seen from this, we now have a `y` for which `0.0 ≤ y[1] ≤ 1.0`, `0.0 < y[2]`, and `sum(y[3:4]) ≈ 1.0`.

docs/src/defining.md

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# Defining a bijector
2+
3+
This page describes the minimum expected interface to implement a bijector.
4+
5+
In general, there are two pieces of information needed to define a bijector:
6+
7+
1. The transformation itself, i.e., the map $b: \mathbb{R}^d \to \mathbb{R}^d$.
8+
9+
2. The log-absolute determinant of the Jacobian of that transformation.
10+
For a transformation $b: \mathbb{R}^d \to \mathbb{R}^d$, the Jacobian at point $x \in \mathbb{R}^d$ is defined as:
11+
12+
$$J_{b}(x) = \begin{bmatrix}
13+
\partial y_1/\partial x_1 & \partial y_1/\partial x_2 & \cdots & \partial y_1/\partial x_d \\
14+
\partial y_2/\partial x_1 & \partial y_2/\partial x_2 & \cdots & \partial y_2/\partial x_d \\
15+
\vdots & \vdots & \ddots & \vdots \\
16+
\partial y_d/\partial x_1 & \partial y_d/\partial x_2 & \cdots & \partial y_d/\partial x_d
17+
\end{bmatrix}$$
18+
19+
where $y = b(x)$.
20+
21+
## The transform itself
22+
23+
The most efficient way to implement a bijector is to provide an implementation of:
24+
25+
```@docs; canonical=false
26+
Bijectors.with_logabsdet_jacobian
27+
```
28+
29+
If you define `with_logabsdet_jacobian(b, x)`, then you will automatically get default implementations of both `transform(b, x)` and `logabsdetjac(b, x)`, which respectively return the first and second value of that tuple.
30+
So, in fact, you can implement a bijector by defining only `with_logabsdet_jacobian`.
31+
32+
If you prefer, you can implement `transform` and `logabsdetjac` separately, as described below.
33+
Having manual implementations of these may also be useful if you expect either to be used heavily without the other.
34+
35+
### Transformation
36+
37+
```@docs; canonical=false
38+
transform
39+
```
40+
41+
If `transform(b, x)` is defined, then you will automatically get a default implementation of `b(x)` which calls that.
42+
43+
### Log-absolute determinant of the Jacobian
44+
45+
```@docs; canonical=false
46+
Bijectors.logabsdetjac
47+
```
48+
49+
## Inverse
50+
51+
Often you will want to define an inverse bijector as well.
52+
To do so, you will have to implement:
53+
54+
```@docs; canonical=false
55+
Bijectors.inverse
56+
```
57+
58+
If `b` is a bijector, then `inverse(b)` should return the inverse bijector $b^{-1}$.
59+
60+
If your bijector subtypes `Bijectors.Bijector`, then you will get a default implementation of `inverse` which constructs `Bijectors.Inverse(b)`.
61+
This may be easier than creating a second type for the inverse bijector.
62+
Note that you will also need to implement the methods for `with_logabsdet_jacobian` (and/or `transform` and `logabsdetjac`) for the inverse bijector type.
63+
64+
If your bijector is not invertible, you can specify this here:
65+
66+
```@docs; canonical=false
67+
Bijectors.isinvertible
68+
```
69+
70+
## Distributions
71+
72+
If your bijector is intended for use with a distribution, i.e., it transforms random variables drawn from that distribution to Euclidean space, then you should also implement:
73+
74+
```@docs; canonical=false
75+
Bijectors.bijector
76+
```
77+
78+
which should return your bijector.
79+
80+
On top of that, you should also implement a method for `Bijectors.output_size(b, dist::Distribution)`:
81+
82+
```@docs; canonical=false
83+
Bijectors.output_size
84+
```
85+
86+
## Closed-form
87+
88+
If your bijector does _not_ have a closed-form expression (e.g. if it uses an iterative procedure), then this should be set to false:
89+
90+
```@docs; canonical=false
91+
Bijectors.isclosedform
92+
```
93+
94+
The default is `true` so you only need to set this if your bijector is not closed-form.

docs/src/distributions.md

Lines changed: 55 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,78 @@
1-
## Basic usage
1+
# Usage with distributions
22

3-
Other than the `logpdf_with_trans` methods, the package also provides a more composable interface through the `Bijector` types. Consider for example the one from above with `Beta(2, 2)`.
3+
Bijectors provides many utilities for working with probability distributions.
44

5-
```julia
6-
julia> using Random;
7-
Random.seed!(42);
5+
```@example distributions
6+
using Bijectors
7+
8+
dist = LogNormal()
9+
x = rand(dist)
10+
b = bijector(dist) # bijection (0, ∞) → ℝ
11+
12+
y = b(x)
13+
```
814

9-
julia> using Bijectors;
10-
using Bijectors: Logit;
15+
Here, `bijector(d::Distribution)` returns the corresponding constrained-to-unconstrained bijection for `Beta`, which is a log function.
16+
The resulting bijector can be called, just like any other function, to transform samples from the distribution to the unconstrained space.
1117

12-
julia> dist = Beta(2, 2)
13-
Beta{Float64}=2.0, β=2.0)
18+
The function [`link`](@ref) provides a short way of doing the above:
1419

15-
julia> x = rand(dist)
16-
0.36888689965963756
20+
```@example distributions
21+
link(dist, x) ≈ b(x)
22+
```
23+
24+
See [the Turing.jl docs](https://turinglang.org/docs/developers/transforms/distributions/) for more information about how this is used in probabilistic programming.
25+
26+
## Transforming distributions
1727

18-
julia> b = bijector(dist) # bijection (0, 1) → ℝ
19-
Logit{Float64}(0.0, 1.0)
28+
We can also couple a distribution together with its bijector to create a _transformed_ `Distribution`, i.e. a `Distribution` defined by sampling from a given `Distribution` and then transforming using a given transformation:
2029

21-
julia> y = b(x)
22-
-0.5369949942509267
30+
```@example distributions
31+
dist = LogNormal() # support on (0, ∞)
32+
tdist = transformed(dist) # support on ℝ
2333
```
2434

25-
In this case we see that `bijector(d::Distribution)` returns the corresponding constrained-to-unconstrained bijection for `Beta`, which indeed is a `Logit` with `a = 0.0` and `b = 1.0`. The resulting `Logit <: Bijector` has a method `(b::Logit)(x)` defined, allowing us to call it just like any other function. Comparing with the above example, `b(x) ≈ link(dist, x)`. Just to convince ourselves:
35+
We can then sample from, and compute the `logpdf` for, the resulting distribution:
36+
37+
```@example distributions
38+
y = rand(tdist)
39+
```
40+
41+
```@example distributions
42+
logpdf(tdist, y)
43+
```
44+
45+
We should expect here that
2646

2747
```julia
28-
julia> b(x) link(dist, x)
29-
true
48+
logpdf(tdist, y) logpdf(dist, x) - logabsdetjac(b, x)
3049
```
3150

32-
## Transforming distributions
51+
where `b = bijector(dist)` and `y = b(x)`.
3352

34-
```@setup transformed-dist-simple
35-
using Bijectors
53+
To verify this, we can calculate the value of `x` using the inverse bijector:
54+
55+
```@example distributions
56+
b = bijector(dist)
57+
binv = inverse(b)
58+
59+
x = binv(y)
3660
```
3761

38-
We can create a _transformed_ `Distribution`, i.e. a `Distribution` defined by sampling from a given `Distribution` and then transforming using a given transformation:
62+
(Because `b` is just a log function, `binv` is an exponential function, i.e. `x = exp(y)`.)
3963

40-
```@repl transformed-dist-simple
41-
dist = Beta(2, 2) # support on (0, 1)
42-
tdist = transformed(dist) # support on ℝ
64+
Then we can check the equality:
4365

44-
tdist isa UnivariateDistribution
66+
```@example distributions
67+
logpdf(tdist, y) ≈ logpdf(dist, x) - logabsdetjac(b, x)
4568
```
4669

47-
We can the then compute the `logpdf` for the resulting distribution:
70+
You can also use [`Bijectors.logpdf_with_trans`](@ref) with the original distribution:
4871

49-
```@repl transformed-dist-simple
50-
# Some example values
51-
x = rand(dist)
52-
y = tdist.transform(x)
72+
```@example distributions
73+
logpdf_with_trans(dist, x, false) ≈ logpdf(dist, x)
74+
```
5375

54-
logpdf(tdist, y)
76+
```@example distributions
77+
logpdf_with_trans(dist, x, true) ≈ logpdf(tdist, y)
5578
```

0 commit comments

Comments
 (0)