A Python implementation of Bayesian Networks from scratch, featuring exact inference (Variable Elimination) and approximate inference algorithms (Rejection Sampling, Gibbs Sampling, and Likelihood Weighting). This project was developed for a graduate-level Generative Models course (M.S. in Computer Science).
- Bayesian Network Class: Build, modify, and visualize directed acyclic graphs (DAGs).
- Exact Inference: Calculates exact probabilities using Variable Elimination and brute-force enumeration.
- Approximate Inference: Estimates probabilities using MCMC and other sampling methods:
- Rejection Sampling
- Gibbs Sampling
- Likelihood Weighting
- Performance Analysis: Includes a comprehensive script to compare the accuracy (vs. ground truth) and execution time of all sampling algorithms.
- Modular & Robust: Code is refactored into a clean
src/directory with logging, error handling, and a robustFactorclass for stable inference.
- Generative Models: Bayesian Networks as a foundational generative model.
- Probabilistic Graphical Models (PGMs): Representing conditional independence in a graph.
- Conditional Probability Tables (CPTs): Quantifying the relationships between variables.
- Exact Inference:
- Variable Elimination: An efficient algorithm for exact inference by summing out variables and manipulating factors.
- Approximate Inference (Sampling):
- Rejection Sampling: A simple method that generates samples from the prior and rejects those inconsistent with evidence.
- Likelihood Weighting: A more efficient method that fixes evidence variables and weights samples by their likelihood.
- Gibbs Sampling (MCMC): A Markov Chain Monte Carlo method that samples from the Markov blanket of each variable.
The project is structured into two main parts: the core library in src/ and the executable experiments in scripts/.
src/bayesian_network.py: Contains the mainBayesianNetworkclass. It usesnetworkxto manage the graph structure and stores CPTs in dictionaries. This class also implements the exact inference methods. Thevariable_eliminationalgorithm is built on aFactorclass to correctly and efficiently multiply, sum-out, and reduce probability tables.src/sampling.py: Provides standalone functions for the three sampling algorithms (rejection_sampling,gibbs_sampling,likelihood_weighting). Each function takes aBayesianNetworkobject, a query, and evidence as input.src/utils.py: Includes asetup_loggingfunction and, most importantly, theFactorclass and its helper functions (multiply_factors,sum_out,reduce_factor). This class is the engine behind the variable elimination algorithm.
scripts/run_inference.py: A command-line script to build the 4-node example network and compute an exact posterior probability using either Variable Elimination (--method ve) or enumeration (--method enumeration).scripts/run_sampling.py: A command-line script to estimate a posterior probability on the example network using one of the three sampling methods.scripts/compare_performance.py: An advanced script that:- Builds the standard 'Asia' Bayesian Network.
- Calculates a ground-truth probability using Variable Elimination.
- Runs all three sampling methods with varying sample sizes (e.g., 100, 1000, 10000, 100000).
- Measures the squared error and execution time for each run.
- Saves the results to
results/performance_results.csvand generates plots for error and time comparison.
This section provides a brief mathematical and conceptual overview of the core inference algorithms implemented.
A Bayesian Network simplifies the calculation of the full joint probability distribution by leveraging conditional independence. The chain rule of probability, applied to a BN, states that the joint probability of any assignment of values
The joint_probability method implements this directly. It iterates through the nodes in topological order, finds the conditional probability of each node's assigned value given its parents' values (from the CPT), and multiplies these probabilities together.
Variable Elimination (VE) computes an exact posterior probability
The goal is to compute
VE works by pushing the summations "inward" as far as possible. To eliminate a variable
This process is repeated until only factors involving Factor class, which handles the multiply_factors and sum_out operations.
This is the most straightforward sampling method. It estimates
- Initialize counts
$N(e) = 0$ and$N(Q, e) = 0$ . - For
$i=1 \to M$ total samples: - Generate a complete sample
$x = (x_1, \dots, x_n)$ from the prior by sampling each$X_i$ in topological order from$P(X_i | \text{Parents}(X_i))$ . -
Reject the sample if it is not consistent with the evidence
$e$ . - If the sample is not rejected (i.e., it matches
$e$ ):- Increment
$N(e)$ . - If the sample also matches the query
$Q$ , increment$N(Q, e)$ .
- Increment
- Return
$\frac{N(Q, e)}{N(e)}$ .
Problem: This is extremely inefficient if the evidence
Likelihood Weighting improves on Rejection Sampling by forcing the evidence variables to take their observed values. To compensate, it weights each sample by the likelihood of that evidence occurring.
- Initialize total weighted sums
$W(e) = 0$ and$W(Q, e) = 0$ . - For
$i=1 \to M$ total samples: - Initialize sample weight
$w = 1.0$ and an empty sample$x$ . - For each variable
$X_i$ in topological order:- If
$X_i$ is an evidence variable with value$e_j$ :- Set
$x_i = e_j$ . - Multiply the weight by the likelihood:
$w \leftarrow w \times P(x_i = e_j | \text{Parents}(x_i))$ .
- Set
- If
$X_i$ is not an evidence variable:- Sample
$x_i$ from$P(X_i | \text{Parents}(x_i))$ .
- Sample
- If
- Add the sample's weight to the total:
$W(e) \leftarrow W(e) + w$ . - If the sample
$x$ matches the query$Q$ , add its weight to the query total:$W(Q, e) \leftarrow W(Q, e) + w$ . - Return
$\frac{W(Q, e)}{W(e)}$ .
This is far more efficient as no samples are rejected, though performance can still degrade if many samples have near-zero weights.
Gibbs Sampling is a Markov Chain Monte Carlo (MCMC) method. It estimates the posterior distribution
- Initialize a state
$x$ by fixing evidence variables$e$ and setting non-evidence variables$H$ to random values. - For
$i=1 \to M$ (total iterations): - For each non-evidence variable
$H_j \in H$ :- Sample a new value $h'j$ from $P(H_j | x{-j})$, where
$x_{-j}$ is all other variables. - Update the state:
$x \leftarrow (x_{-j}, h'_j)$ .
- Sample a new value $h'j$ from $P(H_j | x{-j})$, where
- After a "burn-in" period (e.g., 100 iterations), start collecting the samples
$x$ . - Estimate
$P(Q=q | e)$ by counting the fraction of collected samples where the query$Q$ is true.
The key step is sampling from
We calculate the unnormalized probability for
python-bayesian-network-inference/
├── .gitignore # Ignores standard Python/IDE files
├── LICENSE # MIT License
├── README.md # This file
├── requirements.txt # Project dependencies (networkx, matplotlib, etc.)
├── logs/ # Directory for log files
├── results/ # Output directory for plots and CSVs
├── src/ # Main source code
│ ├── __init__.py
│ ├── bayesian_network.py # The core BayesianNetwork class and exact inference
│ ├── sampling.py # All sampling algorithms
│ └── utils.py # Logging setup and the Factor class for VE
├── scripts/ # Executable scripts
│ ├── run_inference.py # Runs exact inference on the example network
│ ├── run_sampling.py # Runs sampling on the example network
│ └── compare_performance.py # Runs advanced comparison on 'Asia' network
└── run_experiments.ipynb # Jupyter Notebook to run all scripts
-
Clone the Repository:
git clone https://github.com/msmrexe/python-bayesian-network-inference.git cd python-bayesian-network-inference -
Set Up the Environment: It's recommended to use a virtual environment.
python -m venv venv source venv/bin/activate # On Windows, use `venv\Scripts\activate` pip install -r requirements.txt
Note:
pygraphvizis optional for a cleaner graph layout but can be difficult to install. The code will fall back to a simpler layout if it's not found. -
Run Experiments: You can either run the individual scripts from the command line or use the provided Jupyter Notebook.
Option A: Run
run_experiments.ipynb(Recommended)Launch Jupyter and open the notebook to run all experiments sequentially and see the results, including the final comparison plots.
jupyter notebook run_experiments.ipynb
Option B: Run Scripts Manually
Execute the scripts from your terminal.
-
Run Exact Inference:
# Calculate P(P1 | P2=1, P3=0) using Variable Elimination python scripts/run_inference.py --query P1 --evidence "P2:1,P3:0" --method ve
-
Run Sampling:
# Estimate P(P1=1 | P2=1, P3=0) with 50,000 Likelihood Weighting samples python scripts/run_sampling.py --query "P1:1" --evidence "P2:1,P3:0" --method likelihood --samples 50000
-
Run Performance Comparison:
# This will take a minute or two to run python scripts/compare_performance.py --steps 5 --max_samples 100000After it finishes, check the
results/folder for the output CSV and plots.
-
Feel free to connect or reach out if you have any questions!
- Maryam Rezaee
- GitHub: @msmrexe
- Email: ms.maryamrezaee@gmail.com
This project is licensed under the MIT License. See the LICENSE file for full details.