NeuPI: Loading and Evaluating PGMs¶
This notebook demonstrates the foundational capabilities of the neupi library: loading Probabilistic Graphical Models (PGMs) and using them to evaluate the log-likelihood of variable assignments.
We will cover:
Loading a Markov Network (MN) from a
.uaifile.Loading a Sum-Product Network (SPN) from a
.jsonfile.Creating random data assignments.
Evaluating the assignments to compute their log-likelihood using the loaded PGMs.
Setup¶
First, let’s import the necessary components from PyTorch and the neupi library.
[1]:
import torch
from pathlib import Path
import os
# Import the PGM wrappers from neupi
from neupi import MarkovNetwork, SumProductNetwork
# Define the device for computation
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
print(f"Using device: {DEVICE}")
# --- Path Setup ---
# get root path of the project
MN_UAI_PATH = Path("networks") / "mn" / "Segmentation_12.uai"
SPN_JSON_PATH = Path("networks") / "spn" / "nltcs" / "spn.json"
print(f"Markov Network path: {MN_UAI_PATH}")
print(f"Sum-Product Network path: {SPN_JSON_PATH}")
# Check if files exist
assert MN_UAI_PATH.exists(), f"File not found: {MN_UAI_PATH}"
assert SPN_JSON_PATH.exists(), f"File not found: {SPN_JSON_PATH}"
Using device: cpu
Markov Network path: networks/mn/Segmentation_12.uai
Sum-Product Network path: networks/spn/nltcs/spn.json
Part 1: Working with a Markov Network (MN)¶
[2]:
# Load the Markov Network from the .uai file
# The MarkovNetwork class parses the file and constructs the necessary tensors for evaluation.
mn_evaluator = MarkovNetwork(uai_file=str(MN_UAI_PATH), device=DEVICE)
print(f"Successfully loaded Markov Network.")
print(f"Number of variables: {mn_evaluator.num_variables}")
print(f"Number of factors: {len(mn_evaluator.pgm.prob_tables_list)}")
Using 1d factors: False
PGM is pairwise.
Successfully loaded Markov Network.
Number of variables: 229
Number of factors: 851
[3]:
# Create a batch of random assignments for the variables.
# Each assignment is a binary vector where the i-th element represents the state of the i-th variable.
batch_size = 10
num_variables_mn = mn_evaluator.num_variables
# Generate a random tensor of shape (batch_size, num_variables) with values 0 or 1.
mn_assignments = torch.randint(0, 2, (batch_size, num_variables_mn), device=DEVICE)
print("Shape of a random assignment batch:", mn_assignments.shape)
print("First assignment vector:\n", mn_assignments[0])
Shape of a random assignment batch: torch.Size([10, 229])
First assignment vector:
tensor([1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0,
1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0,
0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0,
1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0,
0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0,
0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0,
1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0,
0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1,
0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0,
0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1])
[4]:
# Evaluate the log-likelihood of the assignments.
# The evaluator is a callable object. It returns the log-likelihood (in base e) for each assignment in the batch.
with torch.no_grad():
log_likelihoods_mn = mn_evaluator(mn_assignments)
print(f"Shape of the output tensor: {log_likelihoods_mn.shape}")
print(f"Log-likelihoods for each assignment:\n {log_likelihoods_mn.cpu().numpy()}")
# We can also compute the average log-likelihood across the batch
average_ll_mn = log_likelihoods_mn.mean()
print(f"\nAverage Log-Likelihood (MN): {average_ll_mn.item():.4f}")
Shape of the output tensor: torch.Size([10])
Log-likelihoods for each assignment:
[-556.84625 -563.0156 -557.96216 -533.8548 -526.02734 -568.8582
-574.1767 -557.0375 -540.3271 -597.11346]
Average Log-Likelihood (MN): -557.5219
Part 2: Working with a Sum-Product Network (SPN)¶
[5]:
# Load the Sum-Product Network from the .json file
spn_evaluator = SumProductNetwork(json_file=str(SPN_JSON_PATH), device=DEVICE)
print(f"Successfully loaded Sum-Product Network.")
print(f"Number of variables: {spn_evaluator.num_var}")
Successfully loaded Sum-Product Network.
Number of variables: 16
[6]:
# Create a batch of random assignments for the SPN.
batch_size = 10
num_variables_spn = spn_evaluator.num_var
spn_assignments = torch.randint(0, 2, (batch_size, num_variables_spn), device=DEVICE)
print("Shape of a random assignment batch:", spn_assignments.shape)
print("First assignment vector:\n", spn_assignments[0])
Shape of a random assignment batch: torch.Size([10, 16])
First assignment vector:
tensor([0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1])
[7]:
# Evaluate the log-likelihood of the assignments using the SPN.
with torch.no_grad():
log_likelihoods_spn = spn_evaluator(spn_assignments)
print(f"Shape of the output tensor: {log_likelihoods_spn.shape}")
print(f"Log-likelihoods for each assignment:\n {log_likelihoods_spn.cpu().numpy()}")
# Compute the average log-likelihood
average_ll_spn = log_likelihoods_spn.mean()
print(f"\nAverage Log-Likelihood (SPN): {average_ll_spn.item():.4f}")
Shape of the output tensor: torch.Size([10])
Log-likelihoods for each assignment:
[-17.558258 -16.13565 -9.982204 -9.4680805 -17.460737 -10.921132
-20.856169 -15.421832 -14.226763 -10.325974 ]
Average Log-Likelihood (SPN): -14.2357
Conclusion¶
This notebook demonstrated the basic process of loading different types of PGMs (MarkovNetwork, SumProductNetwork) and using them to compute the log-likelihood of given variable assignments. This evaluation capability is the cornerstone of the neupi library, as it provides the supervision signal for training neural networks.