Skip to content

Commit

Permalink
let marginals return dict
Browse files Browse the repository at this point in the history
  • Loading branch information
GiggleLiu committed Sep 5, 2023
1 parent a066560 commit 365b689
Show file tree
Hide file tree
Showing 7 changed files with 78 additions and 24 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "TensorInference"
uuid = "c2297e78-99bd-40ad-871d-f50e56b81012"
authors = ["Jin-Guo Liu", "Martin Roa Villescas"]
version = "0.3.0"
version = "0.4.0"

[deps]
Artifacts = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"
Expand Down
6 changes: 3 additions & 3 deletions examples/hard-core-lattice-gas/main.jl
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,14 @@ partition_func[]

# The marginal probabilities can be computed with the [`marginals`](@ref) function, which measures how likely a site is occupied.
mars = marginals(pmodel)
show_graph(graph; locs=sites, vertex_colors=[(1-b, 1-b, 1-b) for b in getindex.(mars, 2)], texts=fill("", nv(graph)))
show_graph(graph; locs=sites, vertex_colors=[(b = mars[[i]][2]; (1-b, 1-b, 1-b)) for i in vertices(graph)], texts=fill("", nv(graph)))
# The can see the sites at the corner is more likely to be occupied.
# To obtain two-site correlations, one can set the variables to query marginal probabilities manually.
pmodel2 = TensorNetworkModel(problem, β; mars=[[e.src, e.dst] for e in edges(graph)])
mars = marginals(pmodel2);

# We show the probability that both sites on an edge are not occupied
show_graph(graph; locs=sites, edge_colors=[(b=mar[1, 1]; (1-b, 1-b, 1-b)) for mar in mars], texts=fill("", nv(graph)), edge_line_width=5)
show_graph(graph; locs=sites, edge_colors=[(b = mars[[e.src, e.dst]][1, 1]; (1-b, 1-b, 1-b)) for e in edges(graph)], texts=fill("", nv(graph)), edge_line_width=5)

# ## The most likely configuration
# The MAP and MMAP can be used to get the most likely configuration given an evidence.
Expand Down Expand Up @@ -91,4 +91,4 @@ sum(config2)
# The return value is a matrix, with the columns correspond to different samples.
configs = sample(pmodel3, 1000)
sizes = sum(configs; dims=1)
[count(==(i), sizes) for i=0:34] # counting sizes
[count(==(i), sizes) for i=0:34] # counting sizes
61 changes: 56 additions & 5 deletions src/mar.jl
Original file line number Diff line number Diff line change
Expand Up @@ -124,16 +124,67 @@ end
"""
$(TYPEDSIGNATURES)
Returns the marginal probability distribution of variables.
One can use `get_vars(tn)` to get the full list of variables in this tensor network.
Query the marginals of the variables in a [`TensorNetworkModel`](@ref).
The returned value is a dictionary of variables and their marginals, where a marginal is a joint probability distribution over the associated variables.
By default, the marginals of all individual variables are returned.
The marginal variables to query can be specified when constructing [`TensorNetworkModel`](@ref) as its field `mars`.
It will affect the contraction order of the tensor network.
### Arguments
- `tn`: the [`TensorNetworkModel`](@ref) to query.
- `usecuda`: whether to use CUDA for tensor contraction.
- `rescale`: whether to rescale the tensors during contraction.
### Example
The following example is from [`examples/asia/main.jl`](@ref).
```jldoctest; setup = :(using TensorInference, Random; Random.seed!(0))
julia> model = read_model_file(pkgdir(TensorInference, "examples", "asia", "asia.uai"));
julia> tn = TensorNetworkModel(model; evidence=Dict(1=>0))
TensorNetworkModel{Int64, DynamicNestedEinsum{Int64}, Array{Float64}}
variables: 1 (evidence → 0), 2, 3, 4, 5, 6, 7, 8
contraction time = 2^6.022, space = 2^2.0, read-write = 2^7.077
julia> marginals(tn)
Dict{Vector{Int64}, Vector{Float64}} with 8 entries:
[8] => [0.450138, 0.549863]
[3] => [0.5, 0.5]
[1] => [1.0]
[5] => [0.45, 0.55]
[4] => [0.055, 0.945]
[6] => [0.10225, 0.89775]
[7] => [0.145092, 0.854908]
[2] => [0.05, 0.95]
julia> tn2 = TensorNetworkModel(model; evidence=Dict(1=>0), mars=[[2, 3], [3, 4]])
TensorNetworkModel{Int64, DynamicNestedEinsum{Int64}, Array{Float64}}
variables: 1 (evidence → 0), 2, 3, 4, 5, 6, 7, 8
contraction time = 2^7.781, space = 2^5.0, read-write = 2^8.443
julia> marginals(tn2)
Dict{Vector{Int64}, Matrix{Float64}} with 2 entries:
[2, 3] => [0.025 0.025; 0.475 0.475]
[3, 4] => [0.05 0.45; 0.005 0.495]
```
In this example, we first set the evidence of variable 1 to 0, then we query the marginals of all individual variables.
The returned values is a dictionary, the key are query variables, and the value are the corresponding marginals.
The marginals are vectors, with its entries corresponding to the probability of the variable taking the value 0 and 1, respectively.
For evidence variable 1, the marginal is always `[1.0]`, since it is fixed to 0.
Then we set the marginal variables to query to be variable 2 and 3, and variable 3 and 4, respectively.
The joint marginals may or may not increase the contraction time and space.
Here, the contraction space complexity is increased from 2^2.0 to 2^5.0, and the contraction time complexity is increased from 2^5.977 to 2^7.781.
The output marginals are joint probabilities of the query variables represented by tensors.
"""
function marginals(tn::TensorNetworkModel; usecuda = false, rescale = true)::Vector
function marginals(tn::TensorNetworkModel; usecuda = false, rescale = true)::Dict{Vector{Int}}
# sometimes, the cost can overflow, then we need to rescale the tensors during contraction.
cost, grads = cost_and_gradient(tn.code, adapt_tensors(tn; usecuda, rescale))
@debug "cost = $cost"
if rescale
return LinearAlgebra.normalize!.(getfield.(grads[1:length(tn.mars)], :normalized_value), 1)
return Dict(zip(tn.mars, LinearAlgebra.normalize!.(getfield.(grads[1:length(tn.mars)], :normalized_value), 1)))
else
return LinearAlgebra.normalize!.(grads[1:length(tn.mars)], 1)
return Dict(zip(tn.mars, LinearAlgebra.normalize!.(grads[1:length(tn.mars)], 1)))
end
end
4 changes: 2 additions & 2 deletions test/cuda.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ CUDA.allowscalar(false)
tn = TensorNetworkModel(model; optimizer = TreeSA(ntrials = 1, niters = 2, βs = 1:0.1:40), evidence)
@debug contraction_complexity(tn)
@time marginals2 = marginals(tn; usecuda = true)
@test all(x -> x isa CuArray, marginals2)
@test all(x -> x.second isa CuArray, marginals2)
# for dangling vertices, the output size is 1.
npass = 0
for i in 1:(model.nvars)
npass += (length(marginals2[i]) == 1 && reference_solution[i] == [0.0, 1]) || isapprox(Array(marginals2[i]), reference_solution[i]; atol = 1e-6)
npass += (length(marginals2[[i]]) == 1 && reference_solution[i] == [0.0, 1]) || isapprox(Array(marginals2[[i]]), reference_solution[i]; atol = 1e-6)
end
@test npass == model.nvars
end
Expand Down
2 changes: 1 addition & 1 deletion test/generictensornetworks.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ using GenericTensorNetworks, TensorInference
g = GenericTensorNetworks.Graphs.smallgraph(:petersen)
problem = IndependentSet(g)
model = TensorNetworkModel(problem, β; mars=[[2, 3]])
mars = marginals(model)[1]
mars = marginals(model)[[2, 3]]
problem2 = IndependentSet(g; openvertices=[2,3])
mars2 = TensorInference.normalize!(GenericTensorNetworks.solve(problem2, PartitionFunction(β)), 1)
@test mars mars2
Expand Down
19 changes: 11 additions & 8 deletions test/mar.jl
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ end
# compute marginals
ti_sol = marginals(tn)
ref_sol[collect(keys(evidence))] .= fill([1.0], length(evidence)) # imitate dummy vars
@test isapprox(ti_sol, ref_sol; atol = 1e-5)
@test isapprox([ti_sol[[i]] for i=1:length(ref_sol)], ref_sol; atol = 1e-5)
end

@testset "UAI Reference Solution Comparison" begin
Expand Down Expand Up @@ -63,7 +63,7 @@ end
@debug contraction_complexity(tn)
ti_sol = marginals(tn)
ref_sol[collect(keys(evidence))] .= fill([1.0], length(evidence)) # imitate dummy vars
@test isapprox(ti_sol, ref_sol; atol = 1e-4)
@test isapprox([ti_sol[[i]] for i=1:length(ref_sol)], ref_sol; atol = 1e-4)
end
end
end
Expand Down Expand Up @@ -120,15 +120,18 @@ end
mars = marginals(tnet)
tnet23 = TensorNetworkModel(model; openvars=[2,3])
tnet34 = TensorNetworkModel(model; openvars=[3,4])
@test mars[1] probability(tnet23)
@test mars[2] probability(tnet34)
@test mars[[2 ,3]] probability(tnet23)
@test mars[[3, 4]] probability(tnet34)

tnet1 = TensorNetworkModel(model; mars=[[2, 3], [3, 4]], evidence=Dict(3=>1))
tnet2 = TensorNetworkModel(model; mars=[[2, 3], [3, 4]], evidence=Dict(3=>0))
vars = [[2, 4], [3, 5]]
tnet1 = TensorNetworkModel(model; mars=vars, evidence=Dict(3=>1))
tnet2 = TensorNetworkModel(model; mars=vars, evidence=Dict(3=>0))
mars1 = marginals(tnet1)
mars2 = marginals(tnet2)
update_evidence!(tnet1, Dict(3=>0))
mars1b = marginals(tnet1)
@test !(mars1 mars2)
@test mars1b mars2
for k in vars
@test !(mars1[k] mars2[k])
@test mars1b[k] mars2[k]
end
end
8 changes: 4 additions & 4 deletions test/sampling.jl
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,14 @@ using TensorInference, Test
n = 10000
tnet = TensorNetworkModel(model)
samples = sample(tnet, n)
mars = getindex.(marginals(tnet), 2)
mars = marginals(tnet)
mars_sample = [count(s->s[k]==(1), samples) for k=1:8] ./ n
@test isapprox(mars, mars_sample, atol=0.05)
@test isapprox([mars[[i]][2] for i=1:8], mars_sample, atol=0.05)

# fix the evidence
tnet = TensorNetworkModel(model, optimizer=TreeSA(), evidence=Dict(7=>1))
samples = sample(tnet, n)
mars = getindex.(marginals(tnet), 1)
mars = marginals(tnet)
mars_sample = [count(s->s[k]==(0), samples) for k=1:8] ./ n
@test isapprox([mars[1:6]..., mars[8]], [mars_sample[1:6]..., mars_sample[8]], atol=0.05)
@test isapprox([[mars[[i]][1] for i=1:6]..., mars[[8]][1]], [mars_sample[1:6]..., mars_sample[8]], atol=0.05)
end

0 comments on commit 365b689

Please sign in to comment.