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:

  1. Loading a Markov Network (MN) from a .uai file.

  2. Loading a Sum-Product Network (SPN) from a .json file.

  3. Creating random data assignments.

  4. 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([0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1,
        0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0,
        0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0,
        1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1,
        1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,
        0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0,
        1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0,
        0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1,
        0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1,
        1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 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:
 [-573.5968  -530.14056 -574.7241  -563.06165 -558.4025  -537.7342
 -571.29803 -584.13464 -570.5105  -548.5425 ]

Average Log-Likelihood (MN): -561.2145

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, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 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:
 [-14.169769 -16.99731  -19.282038 -15.70285  -17.740835 -21.26359
 -19.176086 -15.313702 -17.947489 -20.663143]

Average Log-Likelihood (SPN): -17.8257

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.