InferenceReport

Summary

InferenceReport is a Julia package to automatically create reports based on MCMC inference methods.

Installing InferenceReport

  1. If you have not done so, install Julia. Julia 1.9 and higher are supported.
  2. Install InferenceReport using
using Pkg; Pkg.add("InferenceReport")

Usage

InferenceReport's main function is report. It takes one mandatory argument, which at the moment can be either Pigeons' Parallel Tempering (PT) output, or MCMCChains' Chains struct.

We provide an example of each in the next two sections.

From Pigeons

First, run Parallel Tempering (PT) via Pigeons.

Then call report() on the resulting PT struct:

using InferenceReport
using Pigeons

pt = pigeons(
        target = toy_mvn_target(2),
        n_rounds = 4,
        record = [traces; round_trip; record_default()])

report(pt)
───────────────────────────────────────────────────────────────────────────────────────
  scans     restarts      Λ        time(s)    allc(B)  log(Z₁/Z₀)   min(α)     mean(α)
────────── ────────── ────────── ────────── ────────── ────────── ────────── ──────────
        2          0      0.609   3.98e-05   1.16e+04      -1.68      0.705      0.932
        4          0       1.28   5.17e-05   1.84e+04      -1.86      0.527      0.858
        8          0       1.39   5.44e-05   3.34e+04      -2.29      0.458      0.845
       16          1       1.55   6.08e-05   5.82e+04      -2.27      0.646      0.828
───────────────────────────────────────────────────────────────────────────────────────
target_description...[skipped: no description provided]
pair_plot... ✓
trace_plot... ✓
moments... ✓
trace_plot_cumulative... ✓
mpi_standard_out...[skipped: no MPI std out files found]
lcb... ✓
gcb_progress... ✓
logz_progress... ✓
round_trip_progress... ✓
pigeons_summary... ✓
pigeons_inputs... ✓
reproducibility_info...[skipped: missing reproducibility_command]
Report generated at: /home/runner/work/InferenceReport.jl/InferenceReport.jl/docs/build/results/all/2024-02-26-23-36-15-mGJZTe4T

This will generate an HTML report with various useful diagnostic plots and open it in your default browser.

See Examples in the left side bar to see examples of such reports.

See report for more information on the options available.

From MCMCChains

MCMCChains.jl is used to store MCMC samples in packages such as Turing.jl.

Simply pass it into report to generate and open an HTML report:

using InferenceReport
using Turing
using MCMCChains

# example from Turing.jl's webpage
@model function coinflip(; N::Int)
    p ~ Beta(1, 1)
    y ~ filldist(Bernoulli(p), N)
    return y
end;

y = Bool[1, 1, 1, 0, 0, 1]
model = coinflip(; N=length(y)) | (; y)
chain = sample(model, NUTS(), 100; progress=false)

report(chain)
┌ Info: Found initial step size
  ϵ = 3.2
target_description...[skipped: no description provided]
pair_plot... ✓
trace_plot... ✓
moments... ✓
trace_plot_cumulative... ✓
mpi_standard_out...[skipped: only applies to Pigeons]
lcb...[skipped: only applies to Pigeons]
gcb_progress...[skipped: only applies to Pigeons]
logz_progress...[skipped: only applies to Pigeons]
round_trip_progress...[skipped: only applies to Pigeons]
pigeons_summary...[skipped: only applies to Pigeons]
pigeons_inputs...[skipped: only applies to Pigeons]
reproducibility_info...[skipped: missing reproducibility_command]
Report generated at: /home/runner/work/InferenceReport.jl/InferenceReport.jl/docs/build/results/all/2024-02-26-23-38-05-18TKbV9n

Target descriptions

Two methods are available to specify descriptions for target distributions.

First, a markdown description can be passed in as argument:

using InferenceReport
using Pigeons

target = toy_mvn_target(2)
pt = pigeons(; target, n_rounds = 2)

report(pt;
    target_description =
        """
        The model description can use math: ``x^2``.
        """)
┌ Info: Neither traces, disk, nor online recorders included.
   You may not have access to your samples (unless you are using a custom recorder, or maybe you just want log(Z)).
   To add recorders, use e.g. pigeons(target = ..., record = [traces; record_default()])
────────────────────────────────────────────────────────────────────────────
  scans        Λ        time(s)    allc(B)  log(Z₁/Z₀)   min(α)     mean(α)
────────── ────────── ────────── ────────── ────────── ────────── ──────────
        2      0.609   3.01e-05   1.14e+04      -1.68      0.705      0.932
        4       1.28    4.5e-05    1.8e+04      -1.86      0.527      0.858
────────────────────────────────────────────────────────────────────────────
Could not build traces: ErrorException("type NamedTuple has no field traces")
target_description... ✓
pair_plot...[skipped: no traces provided (in Pigeons, use 'record = [traces])]
trace_plot...[skipped: no traces provided (in Pigeons, use 'record = [traces])]
moments...[skipped: no traces provided (in Pigeons, use 'record = [traces])]
trace_plot_cumulative...[skipped: no traces provided (in Pigeons, use 'record = [traces])]
mpi_standard_out...[skipped: no MPI std out files found]
lcb... ✓
gcb_progress... ✓
logz_progress... ✓
round_trip_progress...[skipped: number restarts not recorded]
pigeons_summary... ✓
pigeons_inputs... ✓
reproducibility_info...[skipped: missing reproducibility_command]
Report generated at: /home/runner/work/InferenceReport.jl/InferenceReport.jl/docs/build/results/all/2024-02-26-23-41-44-xHVOcAEE

But often, we may have a family of targets (e.g., in the above example, normals indexed by their dimensionality) and would like the documentation to be automatically generated based on the parameters.

To do so, implement your own pigeons_target_description as done in the following example:

using InferenceReport
using Pigeons

target = toy_mvn_target(2)
pt = pigeons(; target, n_rounds = 2)

const MyTargetType = typeof(target)
InferenceReport.pigeons_target_description(target::MyTargetType) =
    """
    Some description.

    It can use information in the target, e.g. here
    to report that its dimension is: $(target.dim)
    """
┌ Info: Neither traces, disk, nor online recorders included.
   You may not have access to your samples (unless you are using a custom recorder, or maybe you just want log(Z)).
   To add recorders, use e.g. pigeons(target = ..., record = [traces; record_default()])
────────────────────────────────────────────────────────────────────────────
  scans        Λ        time(s)    allc(B)  log(Z₁/Z₀)   min(α)     mean(α)
────────── ────────── ────────── ────────── ────────── ────────── ──────────
        2      0.609   2.42e-05   1.14e+04      -1.68      0.705      0.932
        4       1.28   2.51e-05    1.8e+04      -1.86      0.527      0.858
────────────────────────────────────────────────────────────────────────────

Reproducibility instructions

Simple Pigeons reproducibility instructions can be added to the generated page. This is done via the reproducibility_command argument to report. This should be a string showing how to create an Inputs struct. When reproducibility_command is provided, InferenceReport will combine it to information queried in the current git repository to attempt to string together a mini-script that can be used to reproduce the result.

When the model is very simple, this can be done manually:

using InferenceReport
using Pigeons

inputs = Inputs(target = toy_mvn_target(1), n_rounds = 4, record = [traces])
pt = pigeons(inputs)

report(pt;
    reproducibility_command = "Inputs(target = toy_mvn_target(1), n_rounds = 4, record = [traces])")
──────────────────────────────────────────────────────
  scans        Λ      log(Z₁/Z₀)   min(α)     mean(α)
────────── ────────── ────────── ────────── ──────────
        2      0.417      -1.56      0.782      0.954
        4      0.564     -0.653       0.61      0.937
        8      0.597     -0.833      0.869      0.934
       16      0.686      -1.35      0.729      0.924
──────────────────────────────────────────────────────
target_description... ✓
pair_plot... ✓
trace_plot... ✓
moments... ✓
trace_plot_cumulative... ✓
mpi_standard_out...[skipped: no MPI std out files found]
lcb... ✓
gcb_progress... ✓
logz_progress... ✓
round_trip_progress...[skipped: number restarts not recorded]
pigeons_summary... ✓
pigeons_inputs... ✓
reproducibility_info... ✓
Report generated at: /home/runner/work/InferenceReport.jl/InferenceReport.jl/docs/build/results/all/2024-02-26-23-41-48-vkV8xSJk

However, this approach is not recommended as the duplicated code leads to high risk that the reproducibility instruction drifts apart from the actual command used.

To avoid this, we provide a macro, @reproducible, which, given an expression, produces a pair containing the verbatim expression as well as its string value. Using this we can rewrite the above as:

using InferenceReport
using Pigeons

inputs, reproducibility_command =
        @reproducible Inputs(
            target = toy_mvn_target(1), n_rounds = 4, record = [traces])
pt = pigeons(inputs)

report(pt; reproducibility_command)
──────────────────────────────────────────────────────
  scans        Λ      log(Z₁/Z₀)   min(α)     mean(α)
────────── ────────── ────────── ────────── ──────────
        2      0.417      -1.56      0.782      0.954
        4      0.564     -0.653       0.61      0.937
        8      0.597     -0.833      0.869      0.934
       16      0.686      -1.35      0.729      0.924
──────────────────────────────────────────────────────
target_description... ✓
pair_plot... ✓
trace_plot... ✓
moments... ✓
trace_plot_cumulative... ✓
mpi_standard_out...[skipped: no MPI std out files found]
lcb... ✓
gcb_progress... ✓
logz_progress... ✓
round_trip_progress...[skipped: number restarts not recorded]
pigeons_summary... ✓
pigeons_inputs... ✓
reproducibility_info... ✓
Report generated at: /home/runner/work/InferenceReport.jl/InferenceReport.jl/docs/build/results/all/2024-02-26-23-41-51-e1638OIp

Adding postprocessors

Calling report() triggers a list of postprocessors, each creating a section in the generated report.

To add a custom section in the report, first, create a function taking as input the PostprocessContext which contains all required information such as the MCMC traces.

Here is an example of postprocessor outputting the number of dimensions:

using InferenceReport
using MCMCChains
import InferenceReport: get_chains, default_postprocessors, add_table, add_plot, add_markdown

function report_dim(context)
    chns = get_chains(context)
    params = names(chns, :parameters)
    n_params = length(params)
    add_markdown(context;
        title = "Dimension",
        contents = "The target has $n_params parameters."
    )
end
report_dim (generic function with 1 method)

Various utilities are available to generate contents, see for example add_table, add_plot, add_markdown.

Then, to use the custom postprocessor, add it to the postprocessors argument:

using Pigeons

pt = pigeons(
        target = toy_mvn_target(2),
        n_rounds = 4,
        record = [traces; round_trip; record_default()])

report(pt; postprocessors = [report_dim; default_postprocessors()])
───────────────────────────────────────────────────────────────────────────────────────
  scans     restarts      Λ        time(s)    allc(B)  log(Z₁/Z₀)   min(α)     mean(α)
────────── ────────── ────────── ────────── ────────── ────────── ────────── ──────────
        2          0      0.609   4.04e-05   1.16e+04      -1.68      0.705      0.932
        4          0       1.28   5.62e-05   1.84e+04      -1.86      0.527      0.858
        8          0       1.39   4.31e-05   3.34e+04      -2.29      0.458      0.845
       16          1       1.55   6.71e-05   5.82e+04      -2.27      0.646      0.828
───────────────────────────────────────────────────────────────────────────────────────
report_dim... ✓
target_description... ✓
pair_plot... ✓
trace_plot... ✓
moments... ✓
trace_plot_cumulative... ✓
mpi_standard_out...[skipped: no MPI std out files found]
lcb... ✓
gcb_progress... ✓
logz_progress... ✓
round_trip_progress... ✓
pigeons_summary... ✓
pigeons_inputs... ✓
reproducibility_info...[skipped: missing reproducibility_command]
Report generated at: /home/runner/work/InferenceReport.jl/InferenceReport.jl/docs/build/results/all/2024-02-26-23-41-52-wNioGwSC

Pull requests for additional postprocessors are welcome.

Documenter.jl integration

See our Documenter.jl make file to see how to integrate InferenceReport into a broader documentation page. The key functions used are headless, report_to_docs and as_doc_page.