qiskit-machine-learning
qiskit-machine-learning copied to clipboard
Integrated quantum bayesian inference
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.
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.
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 | |
---|---|
Change from base Build 9017274223: | 0.5% |
Covered Lines: | 2064 |
Relevant Lines: | 2214 |
💛 - 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...
Is there anything else left to do for me? Otherwise please inform me.
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
@smens, @edoaltamura, @oscar-wallis, @OkuyanBoga, @Benjamin-Symons can anybody take a look at the proposed new feature?
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).
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: @.***>
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.
A new release (0.3.0) of qiskit-algorithms was done earlier today so this should once again pass CI
I'm not even sure I can, but should I merge it or do you?