Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Binomial Regression Tests and Example Notebook #403

Merged
merged 9 commits into from
Jan 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "RxInfer"
uuid = "86711068-29c9-4ff7-b620-ae75d7495b3d"
authors = ["Bagaev Dmitry <[email protected]> and contributors"]
version = "3.8.4"
version = "3.9.0"

[deps]
BayesBase = "b4ee3484-f114-42fe-b91c-797d54a0c67e"
Expand Down Expand Up @@ -42,7 +42,7 @@ MacroTools = "0.5.6"
Optim = "1.0.0"
ProgressMeter = "1.0.0"
Random = "1.9"
ReactiveMP = "~4.4.4"
ReactiveMP = "~4.5.0"
Reexport = "1.2.0"
Rocket = "1.8.0"
Static = "0.8.10, 1"
Expand Down
4 changes: 2 additions & 2 deletions codemeta.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@
"downloadUrl": "https://github.com/reactivebayes/RxInfer.jl/releases",
"issueTracker": "https://github.com/reactivebayes/RxInfer.jl/issues",
"name": "RxInfer.jl",
"version": "3.8.4",
"version": "3.9.0",
"description": "Julia package for automated, scalable and efficient Bayesian inference on factor graphs with reactive message passing. ",
"applicationCategory": "Statistics",
"developmentStatus": "active",
"readme": "https://reactivebayes.github.io/RxInfer.jl/stable/",
"softwareVersion": "3.8.4",
"softwareVersion": "3.9.0",
"keywords": [
"Bayesian inference",
"message passing",
Expand Down
2 changes: 1 addition & 1 deletion scripts/format.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ s = ArgParseSettings()
end

commandline_args = parse_args(s)
folders_to_format = ["scripts", "src", "test"]
folders_to_format = ["scripts", "src", "test", "ext"]

overwrite = commandline_args["overwrite"]
formatted = all(map(folder -> JuliaFormatter.format(folder, overwrite = overwrite, verbose = true), folders_to_format))
Expand Down
86 changes: 86 additions & 0 deletions test/models/regression/binomialreg_tests.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
@testitem "Linear regression with BinomialPolya node" begin
using BenchmarkTools, Plots, Dates, LinearAlgebra, StableRNGs

include(joinpath(@__DIR__, "..", "..", "utiltests.jl"))

function generate_synthetic_binomial_data(n_samples::Int, true_beta::Vector{Float64}; seed::Int = 42)
rng = StableRNG(seed)
n_features = length(true_beta)
# Generate design matrix X
X = randn(rng, n_samples, n_features)

# Generate number of trials for each observation
n_trials = rand(rng, 5:20, n_samples)

# Compute logits and probabilities
logits = X * true_beta
probs = 1 ./ (1 .+ exp.(-logits))

# Generate binomial outcomes
y = [rand(rng, Binomial(n_trials[i], probs[i])) for i in 1:n_samples]

return X, y, n_trials
end
n_samples = 1000
n_features = 2
true_beta = [-1.0, 0.6]
n_iterations = 100
n_sims = 20

@model function binomial_model(prior_xi, prior_precision, n_trials, X, y)
β ~ MvNormalWeightedMeanPrecision(prior_xi, prior_precision)
for i in eachindex(y)
y[i] ~ BinomialPolya(X[i], n_trials[i], β) where {dependencies = RequireMessageFunctionalDependencies(β = MvNormalWeightedMeanPrecision(prior_xi, prior_precision))}
end
end

function binomial_inference(binomial_model, iterations, X, y, n_trials, n_features)
return infer(
model = binomial_model(prior_xi = zeros(n_features), prior_precision = diageye(n_features)),
data = (X = X, y = y, n_trials = n_trials),
iterations = iterations,
free_energy = true,
options = (limit_stack_depth = 100,)
)
end

function run_simulation(n_sims::Int, n_samples::Int, true_beta::Vector{Float64}; iterations = n_iterations)
# Storage for results
n_features = length(true_beta)
coverage = Vector{Vector{Float64}}(undef, n_sims)
fes = Vector{Vector{Float64}}(undef, n_sims)
for sim in 1:n_sims
# Generate new dataset
X, y, n_trials = generate_synthetic_binomial_data(n_samples, true_beta, seed = sim)
X = [collect(row) for row in eachrow(X)]

# Run inference
results = binomial_inference(binomial_model, iterations, X, y, n_trials, n_features)
# Extract posterior parameters
post = results.posteriors[:β][end]
m = mean(post)
v = var(post)
estimates = map((x, y) -> Normal(x, sqrt(y)), m, v)
coverage[sim] = map((d, b) -> cdf(d, b), estimates, true_beta)
fes[sim] = results.free_energy
end

return coverage, fes
end

function in_credible_interval(x, lwr = 0.025, upr = 0.975)
return x >= lwr && x <= upr
end

coverage, fes = run_simulation(n_sims, n_samples, true_beta)
for i in 1:n_sims
@test fes[i][end] < fes[i][1]
end
coverages = Vector{Float64}(undef, n_features)
for i in 1:n_features
coverages[i] = sum(in_credible_interval.(getindex.(coverage, i))) / n_sims
@test coverages[i] >= 0.8
end

@test_benchmark "models" "binomialreg" binomial_inference(binomial_model, $n_iterations, $X, $y, $n_trials)
end
Loading