qiskit-machine-learning icon indicating copy to clipboard operation
qiskit-machine-learning copied to clipboard

Integrated quantum bayesian inference

Open proeseler opened this issue 1 year ago • 11 comments

Summary

This pull request introduces the QBayesian class to the Qiskit Machine Learning library. The class implements the Quantum Bayesian Inference algorithm, providing a quantum-based approach to infer probabilities in Bayesian networks. This enhancement aligns with the ongoing efforts to expand the library's capabilities in quantum machine learning algorithms. The algorithm is based on the paper from Low, Guang Hao, Theodore J. Yoder, and Isaac L. Chuang. "Quantum inference on Bayesian networks", Physical Review A 89.6 (2014): 062315.

Details and comments

  • Added QBayesian class in qiskit_machine_learning/algorithms/inference.
  • Included unit tests for the new class in tests/algorithms/inference.
  • Updated documentation to reflect the addition of QBayesian and included a tutorial in docs.

✅ I have added the tests to cover my changes. ✅ I have updated the documentation accordingly. ✅ I have read the CONTRIBUTING document.

proeseler avatar Nov 13 '23 23:11 proeseler

CLA assistant check
All committers have signed the CLA.

CLAassistant avatar Nov 13 '23 23:11 CLAassistant

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

CLAassistant avatar Nov 13 '23 23:11 CLAassistant

Pull Request Test Coverage Report for Build 9032432254

Details

  • 160 of 160 (100.0%) changed or added relevant lines in 3 files are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage increased (+0.5%) to 93.225%

Totals Coverage Status
Change from base Build 9017274223: 0.5%
Covered Lines: 2064
Relevant Lines: 2214

💛 - Coveralls

coveralls avatar Nov 15 '23 16:11 coveralls

Normally this has an "approve and run" button to carry out CI but I do not see it here now - it does this for a first time author until a PR has been merged. I have been clicking that button until now so the CI runs each time. Perhaps it just failed to get triggered by the last commit - maybe commit something again - even an empty commit - and lets see...

woodsp-ibm avatar Nov 28 '23 04:11 woodsp-ibm

Is there anything else left to do for me? Otherwise please inform me.

proeseler avatar Dec 20 '23 13:12 proeseler

Is there anything else left to do for me? Otherwise please inform me.

For me its waiting for others to take a (final) look. Given the time of year now, when people take off time for the holidays, it may take until the New Year before that happens. Thank you for your patience in this respect

woodsp-ibm avatar Dec 21 '23 14:12 woodsp-ibm

@smens, @edoaltamura, @oscar-wallis, @OkuyanBoga, @Benjamin-Symons can anybody take a look at the proposed new feature?

adekusar-drl avatar Jan 08 '24 17:01 adekusar-drl

As a note, to avoid the issue we just had with another PR from last year #728 where I needed to update the copyright dates following it getting merged - see #737 I would suggest updating the copyrights in the set of files changed/added here to include 2024 (if they already have 2 dates then the last one change from 2023 to 2024 - if just has 2023 then it it should become 2023, 2024).

woodsp-ibm avatar Jan 08 '24 20:01 woodsp-ibm

pythonimport numpy as np from qiskit import QuantumCircuit, transpile, assemble from qiskit.utils import QuantumInstance from qiskit.algorithms import IterativeAmplitudeEstimation from sklearn.preprocessing import StandardScaler import time

class BayesianQuantumInference: def init(self, feature_map: QuantumCircuit, quantum_instance: QuantumInstance, bayesian_network: dict, shots: int = 8192, epsilon_target: float = 0.01, alpha: float = 0.05): self.feature_map = feature_map self.quantum_instance = quantum_instance self.bayesian_network = bayesian_network self.shots = shots self.epsilon_target = epsilon_target self.alpha = alpha self.scaler = StandardScaler()

def preprocess_data(self, data: np.ndarray) -> np.ndarray:
    # Normalize and encode input data
    normalized_data = self.scaler.fit_transform(data)
    # Additional encoding steps can be added if necessary
    return normalized_data

def add_encoding_gates(self, circuit: QuantumCircuit, data: np.ndarray):
    num_features = data.shape[1]
    for i in range(num_features):
        feature_values = data[:, i]
        for j, value in enumerate(feature_values):
            # Encode the feature value into the quantum circuit using appropriate gates
            # For simplicity, let's assume a simple encoding scheme such as rotation gates
            circuit.ry(value, j)

def construct_circuit(self, data: np.ndarray) -> QuantumCircuit:
    # Preprocess input data
    processed_data = self.preprocess_data(data)

    # Construct quantum circuit for Bayesian inference
    circuit = self.feature_map.copy()

    # Add gates for encoding preprocessed data
    self.add_encoding_gates(circuit, processed_data)
    
    return circuit

def execute_circuit(self, circuit: QuantumCircuit) -> np.ndarray:
    backend = self.quantum_instance.backend
    transpiled_circuit = transpile(circuit, backend)
    qobj = assemble(transpiled_circuit, shots=self.shots)
    job = backend.run(qobj)
    result = job.result()
    return result.get_counts()

def perform_inference(self, data: np.ndarray) -> np.ndarray:
    circuit = self.construct_circuit(data)
    inference_results = self.execute_circuit(circuit)
    return inference_results

def estimate_probability(self, circuit: QuantumCircuit) -> float:
    # Use Iterative Amplitude Estimation for probability estimation
    iae = IterativeAmplitudeEstimation(epsilon_target=self.epsilon_target, alpha=self.alpha)
    result = iae.estimate(problem=circuit)
    return result.estimation

def probability_of_positive_outcome(self, data: np.ndarray) -> float:
    circuit = self.construct_circuit(data)
    probability = self.estimate_probability(circuit)
    return probability

def validate_input(self, data: np.ndarray):
    if not isinstance(data, np.ndarray):
        raise ValueError("Input data must be a NumPy array.")
    if data.ndim != 2:
        raise ValueError("Input data must be a 2-dimensional array.")
    # Additional validation logic can be added as needed

def post_process_results(self, results: np.ndarray) -> dict:
    # Compute probabilities from inference results
    total_shots = sum(results.values())
    probabilities = {key: value / total_shots for key, value in results.items()}
    return probabilities

def handle_errors(self, error):
    # Handle errors gracefully and provide informative error messages
    print("An error occurred during the quantum inference process:")
    print(error)
    # Additional error handling logic can be added as needed

def profile_performance(self):
    # Profile performance of key operations
    start_time = time.time()

    # Measure time for constructing a quantum circuit
    data = np.random.rand(10, 2)  # Example input data
    circuit = self.construct_circuit(data)

    # Measure time for executing a quantum circuit
    execution_results = self.execute_circuit(circuit)

    # Measure time for performing inference
    inference_results = self.perform_inference(data)

    # Measure time for estimating probability
    probability = self.probability_of_positive_outcome(data)

    end_time = time.time()
    execution_time = end_time - start_time

    print("Performance profiling results:")
    print(f"Construction time: {execution_time} seconds")Copy codeimport numpy as np from qiskit import QuantumCircuit, transpile, assemble fromqiskit.utils import QuantumInstance from qiskit.algorithms importIterativeAmplitudeEstimation from sklearn.preprocessing import StandardScaler import timeclass BayesianQuantumInference: def __init__(self, feature_map: QuantumCircuit, quantum_instance: QuantumInstance, bayesian_network: dict, shots: int = 8192, epsilon_target: float = 0.01, alpha: float = 0.05): self.feature_map = feature_map self.quantum_instance = quantum_instance self.bayesian_network = bayesian_network self.shots = shots self.epsilon_target = epsilon_target self.alpha = alpha self.scaler = StandardScaler() def preprocess_data(self, data: np.ndarray) -> np.ndarray: # Normalize and encode input data normalized_data = self.scaler.fit_transform(data) # Additional encoding steps can be added if necessary return normalized_data defadd_encoding_gates(self, circuit: QuantumCircuit, data: np.ndarray): num_features = data.shape[1] for i in range(num_features): feature_values = data[:, i] for j, value inenumerate(feature_values): # Encode the feature value into the quantum circuit using appropriate gates # For simplicity, let's assume a simple encoding scheme such as rotation gates circuit.ry(value, j) def construct_circuit(self, data: np.ndarray) -> QuantumCircuit: # Preprocess input data processed_data = self.preprocess_data(data) # Construct quantum circuit for Bayesian inference circuit = self.feature_map.copy() # Add gates for encoding preprocessed data self.add_encoding_gates(circuit, processed_data)return circuit def execute_circuit(self, circuit: QuantumCircuit) -> np.ndarray: backend = self.quantum_instance.backend transpiled_circuit = transpile(circuit, backend) qobj = assemble(transpiled_circuit, shots=self.shots) job = backend.run(qobj) result = job.result() return result.get_counts() def perform_inference(self, data: np.ndarray) -> np.ndarray: circuit = self.construct_circuit(data) inference_results = self.execute_circuit(circuit) return inference_results def estimate_probability(self, circuit: QuantumCircuit) -> float: # Use Iterative Amplitude Estimation for probability estimation iae = IterativeAmplitudeEstimation(epsilon_target=self.epsilon_target, alpha=self.alpha) result = iae.estimate(problem=circuit) return result.estimation defprobability_of_positive_outcome(self, data: np.ndarray) -> float: circuit = self.construct_circuit(data) probability = self.estimate_probability(circuit) returnprobability def validate_input(self, data: np.ndarray): if not isinstance(data, np.ndarray): raise ValueError("Input data must be a NumPy array.") if data.ndim != 2:raise ValueError("Input data must be a 2-dimensional array.") # Additional validation logic can be added as needed def post_process_results(self, results: np.ndarray) -> dict:# Compute probabilities from inference results total_shots = sum(results.values()) probabilities = {key: value / total_shots for key, value in results.items()} returnprobabilities def handle_errors(self, error): # Handle errors gracefully and provide informative error messages print("An error occurred during the quantum inference process:") print(error) # Additional error handling logic can be added as needed defprofile_performance(self): # Profile performance of key operations start_time = time.time() # Measure time for constructing a quantum circuit data = np.random.rand(10, 2)  # Example input data circuit = self.construct_circuit(data) # Measure time for executing a quantum circuit execution_results = self.execute_circuit(circuit) # Measure time for performing inference inference_results = self.perform_inference(data) # Measure time for estimating probability probability = self.probability_of_positive_outcome(data) end_time = time.time() execution_time = end_time - start_time print("Performance profiling results:") print(f"Construction time: {execution_time} seconds")Sent from my iPhoneOn 13. 2. 2024., at 01:32, M. Emre Sahin ***@***.***> wrote:

@OkuyanBoga commented on this pull request.

In docs/tutorials/13_quantum_bayesian_inference.ipynb:

  • "source": [
  • "## 1. Introduction"
  • ]
  • },
  • {
  • "cell_type": "markdown",
  • "source": [
  • "### 1.1. Quantum vs. Classical Bayesian Inference\n",
  • "\n",
  • "Bayesian networks, or belief networks, are graphical models that illustrate probabilistic relationships between variables using nodes (representing variables) and edges (indicating conditional dependencies) in a directed acyclic graph. Each node is associated with conditional probability tables (CPTs) that detail the influence of parent nodes on their children. \n",
  • "\n",
  • "In these networks, Bayesian inference is key for updating probabilities. It employs Bayes' theorem to revise the likelihood of hypotheses based on new data, considering the network's variable interdependencies. For instance, in a network assessing diseases based on symptoms, observing new symptoms allows for recalculating disease probabilities. This recalibration combines the disease's prior probability with the observed symptom likelihood, leading to an updated, more precise disease probability. Thus, Bayesian inference is a dynamic process of adjusting our understanding of one variable in light of new information about others, facilitating informed, evidence-based decisions.\n",
  • "\n",
  • "Exact inference on Bayesian networks is \#P-hard. That is why usually approximate inference is used to sample from the distribution on query variables given evidence variables. QBI efficiently utilizes the structure of Bayesian networks represented by a quantum circuit that represent the probability distributions. By employing a quantum version of rejection sampling and leveraging amplitude amplification, quantum computation achieves a significant speedup, making it possible to obtain samples much faster. \n",
  • "\n",
  • "This tutorial will guide you through the process of using the QBayesian class to perform such inference tasks. This inference algorithm implements the algorithm from the paper "Quantum inference on Bayesian networks" by Low, Guang Hao et al. This leads to a speedup per sample from $O(nmP(e)^{-1})$ to $O(n2^{m}P(e)^{-\frac{1}{2}})$, where n is the number of nodes in the Bayesian network with at most m parents per node and e the evidence.\n",

I think it is ready after adressing:

As a note, to avoid the issue we just had with another PR from last year #728 where I needed to update the copyright dates following it getting merged - see #737 I would suggest updating the copyrights in the set of files changed/added here to include 2024 (if they already have 2 dates then the last one change from 2023 to 2024 - if just has 2023 then it it should become 2023, 2024).

—Reply to this email directly, view it on GitHub, or unsubscribe.You are receiving this because you are subscribed to this thread.Message ID: @.***>

Jrbiltmore avatar Feb 16 '24 10:02 Jrbiltmore

I let it start CI. It will fail across the board though. Qiskit 1.0 released yesterday and while Qiskit Algorithms has a couple of fixes in places for breaking changes it has not yet released and we end up with failures here. Also in the tutorials here which are all breaking and I have started fixing elsewhere. So basically please hang in there while we get this sorted - hopefully early next week it will be working again.

woodsp-ibm avatar Feb 16 '24 18:02 woodsp-ibm

A new release (0.3.0) of qiskit-algorithms was done earlier today so this should once again pass CI

woodsp-ibm avatar Feb 19 '24 17:02 woodsp-ibm

I'm not even sure I can, but should I merge it or do you?

proeseler avatar May 11 '24 12:05 proeseler