Techno Blender
Digitally Yours.

Do Discretized Qubits Work In Practice? | by Frank Zickert | Quantum Machine Learning | Jan, 2023

0 38


Machine learning models have become increasingly complex and, therefore, hard to train. Take ChatGPT, for instance. On a single GPU, its training would have taken 355 years.

Quantum computing is a promising technology that could accelerate the training of such models. Yet, it comes with its own set of challenges.

Quantum bits (qubits) are the basic unit we use inside a quantum computer. Unlike classical bits, which are either 0 or 1, quantum bits are in a complex linear relationship between their two basis states |0⟩ and |1⟩, called superposition.

Image by author

This makes them extremely powerful. First, this relationship is not discrete but continuous, meaning the qubit can assume any value between the two basis states. Second, the relationship builds upon complex numbers — these are two-dimensional constructs — which exceed the capabilities of the one-dimensional numbers we’re used to working with.

But there’s a problem. There always is — unfortunately.

Once we measure a qubit, it collapses to either of its basis states. Inevitably, all we see is 0 or 1.

Moreover, we only have very few qubits. And the qubits we have are prone to errors. We say they are noisy.

Obviously, we — the algorithm developers — can’t increase the number of available qubits. This challenge remains with the hardware manufacturers such as IBM. But, we can use the few qubits we have as efficiently as possible.

To this end, I have proposed discretizing the expectation value and thus encoding more than two values by a qubit.

The expectation value results from the repeated execution of a circuit and measuring the qubit. So, for instance, if a qubit is 1 in only three out of ten executions, its expectation value is 0.3.

The following function takes the counts object that results from running a quantum circuit in Qiskit — IBM’s quantum development kit. The blocks parameter denotes the number of discrete values we want to use.

def discretize(counts, blocks):
weigthed = 0
sum_count = 0
print (counts)
for key, value in counts.items():
weigthed += int(key)*int(value*0.999*blocks)
sum_count += value
return int(weigthed/sum_count)

This function loops through all items in the counts object. These items are key-value pairs, such as 0: 300 where the key (0) represents the measurement, and the value (300) represents the number of times we observed it.

We multiply each key with its value and multiply that by the number of blocks and decrease that value a little bit (multiply by 0.999). This means we regard a value that resides at the exact line between two discrete values as the lower one.

The overall discretized value is the total sum of all weighted values divided by the total sum of all unweighted values.

This is but a short function. Yet, let’s see how it performs in practice.

First, we write a helper function that creates the counts object for us.

from math import asin, sqrt
from qiskit import QuantumCircuit, execute, Aer
from qiskit.visualization import plot_histogram

def prob_to_angle(prob):
return 2*asin(sqrt(prob))

def simulate_step(blocks, steps, current):
qc = QuantumCircuit(1)

qc.ry(prob_to_angle((current+0.5)/steps), 0)

# Tell Qiskit how to simulate our circuit
backend = Aer.get_backend('statevector_simulator')

# Do the simulation, returning the result
result = execute(qc,backend, shots=1000).result()

# get the probability distribution
counts = result.get_counts()

return discretize(counts, blocks)

This function takes the number of blocks, the steps, and the current step as parameters. I’ll explain these in a minute. First, let’s look into the definition of the function. We create a quantum circuit with a single qubit and apply a rotation around the Y-axis.

This post explains this operator in detail.

Essentially, the ry gate rotates the default state |0⟩ to a state that represents a certain probability of measuring the qubit as 1. Here, this probability is (current+0.5)/steps.

Then, we define a backend and execute the quantum circuit to obtain the result that provides the counts that we discretize.

The behavior of this helper function becomes apparent when we use it.

import collections

blocks = 4
steps = 20
values = [simulate_step(blocks, steps, x) for x in range(steps)]
print (values)

sorted(collections.Counter(values).items(), key=lambda x: x[0])

The output of this code is the following:

{'0': 0.975, '1': 0.025}
{'0': 0.925, '1': 0.075}
{'0': 0.875, '1': 0.125}
{'0': 0.825, '1': 0.175}
{'0': 0.775, '1': 0.225}
{'0': 0.725, '1': 0.275}
{'0': 0.675, '1': 0.325}
{'0': 0.625, '1': 0.375}
{'0': 0.575, '1': 0.425}
{'0': 0.525, '1': 0.475}
{'0': 0.475, '1': 0.525}
{'0': 0.425, '1': 0.575}
{'0': 0.375, '1': 0.625}
{'0': 0.325, '1': 0.675}
{'0': 0.275, '1': 0.725}
{'0': 0.225, '1': 0.775}
{'0': 0.175, '1': 0.825}
{'0': 0.125, '1': 0.875}
{'0': 0.075, '1': 0.925}
{'0': 0.025, '1': 0.975}
[0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3]
[(0, 5), (1, 5), (2, 5), (3, 5)]

So, what happens here?

First, we define to discretize the expectation value of our qubit into four blocks. We want to evaluate this function with 20 steps. So, we run the circuit for each current step (x).

The first rows of the output denote the counts printed inside discretize function. As you see, the probability of measuring the qubit as 1 increases as the probability of measuring it as 0 decreases. The probability represents the ratio between the current step and the total number of steps. This is what we calculate by prob_to_angle((current+0.5)/steps).

The next line shows the list of discretized values. As we see, the first five items correspond to the value 0, the next five to the value 1 and so on.

The last output shows the distribution of discretized values. Each value from 0 to 3 appears five times. Since we have 20 steps, is demonstrates that the discretization works fine.

But this is not a real test yet. We used the statevector_simulator that prepares the perfect quantum state and the exact counts corresponding to it.

But, measuring quantum states can only be done empirically in reality. We can’t compute the perfect quantum state of a larger quantum circuit. If we could, there was no reason to build and use a quantum computer.

So, the next helper function uses the qasm_simulator that creates the counts empirically.

def simulate_step_qasm(blocks, steps, current):
qc = QuantumCircuit(1)

qc.ry(prob_to_angle((current+0.5)/steps), 0)
qc.measure_all()

# Tell Qiskit how to simulate our circuit
backend = Aer.get_backend('qasm_simulator')

# Do the simulation, returning the result
result = execute(qc,backend, shots=1000).result()

# get the probability distribution
counts = result.get_counts()

return discretize(counts, blocks)

There are only two differences. First, we use the qasm_simulator instead of the statevector_simulator. Second, we measure the qubits after we applied the ry gate.

Let’s look at the results.

values = [simulate_step_qasm(blocks, steps, x) for x in range(steps)]
print (values)
sorted(collections.Counter(values).items(), key=lambda x: x[0])
{'1': 29, '0': 971}
{'1': 74, '0': 926}
{'1': 130, '0': 870}
{'1': 161, '0': 839}
{'1': 218, '0': 782}
{'0': 742, '1': 258}
{'1': 325, '0': 675}
{'0': 621, '1': 379}
{'0': 599, '1': 401}
{'0': 506, '1': 494}
{'1': 516, '0': 484}
{'0': 447, '1': 553}
{'0': 390, '1': 610}
{'0': 318, '1': 682}
{'0': 284, '1': 716}
{'0': 225, '1': 775}
{'0': 169, '1': 831}
{'1': 871, '0': 129}
{'0': 67, '1': 933}
{'0': 20, '1': 980}
[0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3]
[(0, 5), (1, 5), (2, 5), (3, 5)]

We see that the counts are not as perfectly distributed anymore. However, the discretization still works. We see each discrete value exactly five times.

But we are not there yet. Even though we see statistical deviations in the results, the qasm_simulator simulates a noise-free quantum computer. But we do not have such devices yet.

Instead, our current devices are prone to errors. They are noisy. So, let’s add some noise.

from qiskit import transpile
from qiskit.providers.fake_provider import FakeQuito
from qiskit.providers.aer import AerSimulator

# create a fake backend
device_backend = FakeQuito()

# create a simulator from the backend
sim_quito = AerSimulator.from_backend(device_backend)

def simulate_step_noise(blocks, steps, current):
qc = QuantumCircuit(1)

qc.ry(prob_to_angle((current+0.5)/steps), 0)
qc.measure_all()

# Tell Qiskit how to simulate our circuit
# transpile the circuit
mapped_circuit = transpile(qc, backend=sim_quito)

# run the transpiled circuit, no need to assemble it
result = sim_quito.run(mapped_circuit, shots=1000).result()

# get the probability distribution
counts = result.get_counts()

return discretize(counts, blocks)

While the circuit remains the same, we create another backend this time. We create a simulator that exhibits the same noise as IBM’s quantum computer in Quito. This is a five-qubit quantum computer available through the IBM cloud.

values = [simulate_step_noise(blocks, steps, x) for x in range(steps)]
print (values)
sorted(collections.Counter(values).items(), key=lambda x: x[0])
{'1': 55, '0': 945}
{'1': 83, '0': 917}
{'0': 855, '1': 145}
{'1': 177, '0': 823}
{'1': 230, '0': 770}
{'0': 700, '1': 300}
{'1': 319, '0': 681}
{'0': 619, '1': 381}
{'0': 617, '1': 383}
{'0': 539, '1': 461}
{'0': 496, '1': 504}
{'1': 541, '0': 459}
{'0': 407, '1': 593}
{'1': 623, '0': 377}
{'1': 663, '0': 337}
{'0': 284, '1': 716}
{'0': 219, '1': 781}
{'0': 178, '1': 822}
{'1': 862, '0': 138}
{'0': 80, '1': 920}
[0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3]
[(0, 5), (1, 5), (2, 6), (3, 4)]

We see the counts to deviate even further from the correct values. And as a consequence, we see that we get one wrong discretization. We take one 3 for a 2, in the case of {0: 284, 1: 716}. Certainly, we would not have seen an error if we only interpreted the counts as either 0 or 1. The item in question certainly is rather 1 than 0.

Otherwise, the result is not too bad. In fact, let’s see what happens if we interpret the expectation value not as four but as eight values.

steps = 24
blocks = 8
values = [simulate_step_noise(blocks, steps, x) for x in range(steps)]
print (values)
sorted(collections.Counter(values).items(), key=lambda x: x[0])
{'1': 45, '0': 955}
{'1': 71, '0': 929}
{'1': 132, '0': 868}
{'1': 134, '0': 866}
{'0': 811, '1': 189}
{'1': 202, '0': 798}
{'1': 266, '0': 734}
{'0': 700, '1': 300}
{'1': 325, '0': 675}
{'1': 368, '0': 632}
{'1': 413, '0': 587}
{'1': 440, '0': 560}
{'1': 486, '0': 514}
{'1': 530, '0': 470}
{'1': 580, '0': 420}
{'1': 623, '0': 377}
{'0': 354, '1': 646}
{'0': 301, '1': 699}
{'0': 275, '1': 725}
{'0': 226, '1': 774}
{'0': 213, '1': 787}
{'1': 825, '0': 175}
{'0': 121, '1': 879}
{'0': 97, '1': 903}
[0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7]
[(0, 2), (1, 4), (2, 4), (3, 3), (4, 3), (5, 3), (6, 3), (7, 2)]

We would expect to see each discretized value exactly three times. So, even though the results are not entirely unreasonable, we see a significant number of errors here.

So, discretized expectation values increase the susceptibility to noise. But, error mitigation techniques, such as the zero-noise extrapolation technique (ZNE) or Clifford Data Regression (CDR), help us mitigate the negative effect of noise.

Conclusion

Nevertheless, the ability to encode four or even eight instead of only two values could be a decisive factor when using current quantum computers. While we can’t easily increase the number of available qubits, we can use appropriate techniques, such as discretization. When combined with error mitigation techniques, we could possibly make our tiny number of qubits appear much bigger.


Machine learning models have become increasingly complex and, therefore, hard to train. Take ChatGPT, for instance. On a single GPU, its training would have taken 355 years.

Quantum computing is a promising technology that could accelerate the training of such models. Yet, it comes with its own set of challenges.

Quantum bits (qubits) are the basic unit we use inside a quantum computer. Unlike classical bits, which are either 0 or 1, quantum bits are in a complex linear relationship between their two basis states |0⟩ and |1⟩, called superposition.

Image by author

This makes them extremely powerful. First, this relationship is not discrete but continuous, meaning the qubit can assume any value between the two basis states. Second, the relationship builds upon complex numbers — these are two-dimensional constructs — which exceed the capabilities of the one-dimensional numbers we’re used to working with.

But there’s a problem. There always is — unfortunately.

Once we measure a qubit, it collapses to either of its basis states. Inevitably, all we see is 0 or 1.

Moreover, we only have very few qubits. And the qubits we have are prone to errors. We say they are noisy.

Obviously, we — the algorithm developers — can’t increase the number of available qubits. This challenge remains with the hardware manufacturers such as IBM. But, we can use the few qubits we have as efficiently as possible.

To this end, I have proposed discretizing the expectation value and thus encoding more than two values by a qubit.

The expectation value results from the repeated execution of a circuit and measuring the qubit. So, for instance, if a qubit is 1 in only three out of ten executions, its expectation value is 0.3.

The following function takes the counts object that results from running a quantum circuit in Qiskit — IBM’s quantum development kit. The blocks parameter denotes the number of discrete values we want to use.

def discretize(counts, blocks):
weigthed = 0
sum_count = 0
print (counts)
for key, value in counts.items():
weigthed += int(key)*int(value*0.999*blocks)
sum_count += value
return int(weigthed/sum_count)

This function loops through all items in the counts object. These items are key-value pairs, such as 0: 300 where the key (0) represents the measurement, and the value (300) represents the number of times we observed it.

We multiply each key with its value and multiply that by the number of blocks and decrease that value a little bit (multiply by 0.999). This means we regard a value that resides at the exact line between two discrete values as the lower one.

The overall discretized value is the total sum of all weighted values divided by the total sum of all unweighted values.

This is but a short function. Yet, let’s see how it performs in practice.

First, we write a helper function that creates the counts object for us.

from math import asin, sqrt
from qiskit import QuantumCircuit, execute, Aer
from qiskit.visualization import plot_histogram

def prob_to_angle(prob):
return 2*asin(sqrt(prob))

def simulate_step(blocks, steps, current):
qc = QuantumCircuit(1)

qc.ry(prob_to_angle((current+0.5)/steps), 0)

# Tell Qiskit how to simulate our circuit
backend = Aer.get_backend('statevector_simulator')

# Do the simulation, returning the result
result = execute(qc,backend, shots=1000).result()

# get the probability distribution
counts = result.get_counts()

return discretize(counts, blocks)

This function takes the number of blocks, the steps, and the current step as parameters. I’ll explain these in a minute. First, let’s look into the definition of the function. We create a quantum circuit with a single qubit and apply a rotation around the Y-axis.

This post explains this operator in detail.

Essentially, the ry gate rotates the default state |0⟩ to a state that represents a certain probability of measuring the qubit as 1. Here, this probability is (current+0.5)/steps.

Then, we define a backend and execute the quantum circuit to obtain the result that provides the counts that we discretize.

The behavior of this helper function becomes apparent when we use it.

import collections

blocks = 4
steps = 20
values = [simulate_step(blocks, steps, x) for x in range(steps)]
print (values)

sorted(collections.Counter(values).items(), key=lambda x: x[0])

The output of this code is the following:

{'0': 0.975, '1': 0.025}
{'0': 0.925, '1': 0.075}
{'0': 0.875, '1': 0.125}
{'0': 0.825, '1': 0.175}
{'0': 0.775, '1': 0.225}
{'0': 0.725, '1': 0.275}
{'0': 0.675, '1': 0.325}
{'0': 0.625, '1': 0.375}
{'0': 0.575, '1': 0.425}
{'0': 0.525, '1': 0.475}
{'0': 0.475, '1': 0.525}
{'0': 0.425, '1': 0.575}
{'0': 0.375, '1': 0.625}
{'0': 0.325, '1': 0.675}
{'0': 0.275, '1': 0.725}
{'0': 0.225, '1': 0.775}
{'0': 0.175, '1': 0.825}
{'0': 0.125, '1': 0.875}
{'0': 0.075, '1': 0.925}
{'0': 0.025, '1': 0.975}
[0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3]
[(0, 5), (1, 5), (2, 5), (3, 5)]

So, what happens here?

First, we define to discretize the expectation value of our qubit into four blocks. We want to evaluate this function with 20 steps. So, we run the circuit for each current step (x).

The first rows of the output denote the counts printed inside discretize function. As you see, the probability of measuring the qubit as 1 increases as the probability of measuring it as 0 decreases. The probability represents the ratio between the current step and the total number of steps. This is what we calculate by prob_to_angle((current+0.5)/steps).

The next line shows the list of discretized values. As we see, the first five items correspond to the value 0, the next five to the value 1 and so on.

The last output shows the distribution of discretized values. Each value from 0 to 3 appears five times. Since we have 20 steps, is demonstrates that the discretization works fine.

But this is not a real test yet. We used the statevector_simulator that prepares the perfect quantum state and the exact counts corresponding to it.

But, measuring quantum states can only be done empirically in reality. We can’t compute the perfect quantum state of a larger quantum circuit. If we could, there was no reason to build and use a quantum computer.

So, the next helper function uses the qasm_simulator that creates the counts empirically.

def simulate_step_qasm(blocks, steps, current):
qc = QuantumCircuit(1)

qc.ry(prob_to_angle((current+0.5)/steps), 0)
qc.measure_all()

# Tell Qiskit how to simulate our circuit
backend = Aer.get_backend('qasm_simulator')

# Do the simulation, returning the result
result = execute(qc,backend, shots=1000).result()

# get the probability distribution
counts = result.get_counts()

return discretize(counts, blocks)

There are only two differences. First, we use the qasm_simulator instead of the statevector_simulator. Second, we measure the qubits after we applied the ry gate.

Let’s look at the results.

values = [simulate_step_qasm(blocks, steps, x) for x in range(steps)]
print (values)
sorted(collections.Counter(values).items(), key=lambda x: x[0])
{'1': 29, '0': 971}
{'1': 74, '0': 926}
{'1': 130, '0': 870}
{'1': 161, '0': 839}
{'1': 218, '0': 782}
{'0': 742, '1': 258}
{'1': 325, '0': 675}
{'0': 621, '1': 379}
{'0': 599, '1': 401}
{'0': 506, '1': 494}
{'1': 516, '0': 484}
{'0': 447, '1': 553}
{'0': 390, '1': 610}
{'0': 318, '1': 682}
{'0': 284, '1': 716}
{'0': 225, '1': 775}
{'0': 169, '1': 831}
{'1': 871, '0': 129}
{'0': 67, '1': 933}
{'0': 20, '1': 980}
[0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3]
[(0, 5), (1, 5), (2, 5), (3, 5)]

We see that the counts are not as perfectly distributed anymore. However, the discretization still works. We see each discrete value exactly five times.

But we are not there yet. Even though we see statistical deviations in the results, the qasm_simulator simulates a noise-free quantum computer. But we do not have such devices yet.

Instead, our current devices are prone to errors. They are noisy. So, let’s add some noise.

from qiskit import transpile
from qiskit.providers.fake_provider import FakeQuito
from qiskit.providers.aer import AerSimulator

# create a fake backend
device_backend = FakeQuito()

# create a simulator from the backend
sim_quito = AerSimulator.from_backend(device_backend)

def simulate_step_noise(blocks, steps, current):
qc = QuantumCircuit(1)

qc.ry(prob_to_angle((current+0.5)/steps), 0)
qc.measure_all()

# Tell Qiskit how to simulate our circuit
# transpile the circuit
mapped_circuit = transpile(qc, backend=sim_quito)

# run the transpiled circuit, no need to assemble it
result = sim_quito.run(mapped_circuit, shots=1000).result()

# get the probability distribution
counts = result.get_counts()

return discretize(counts, blocks)

While the circuit remains the same, we create another backend this time. We create a simulator that exhibits the same noise as IBM’s quantum computer in Quito. This is a five-qubit quantum computer available through the IBM cloud.

values = [simulate_step_noise(blocks, steps, x) for x in range(steps)]
print (values)
sorted(collections.Counter(values).items(), key=lambda x: x[0])
{'1': 55, '0': 945}
{'1': 83, '0': 917}
{'0': 855, '1': 145}
{'1': 177, '0': 823}
{'1': 230, '0': 770}
{'0': 700, '1': 300}
{'1': 319, '0': 681}
{'0': 619, '1': 381}
{'0': 617, '1': 383}
{'0': 539, '1': 461}
{'0': 496, '1': 504}
{'1': 541, '0': 459}
{'0': 407, '1': 593}
{'1': 623, '0': 377}
{'1': 663, '0': 337}
{'0': 284, '1': 716}
{'0': 219, '1': 781}
{'0': 178, '1': 822}
{'1': 862, '0': 138}
{'0': 80, '1': 920}
[0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3]
[(0, 5), (1, 5), (2, 6), (3, 4)]

We see the counts to deviate even further from the correct values. And as a consequence, we see that we get one wrong discretization. We take one 3 for a 2, in the case of {0: 284, 1: 716}. Certainly, we would not have seen an error if we only interpreted the counts as either 0 or 1. The item in question certainly is rather 1 than 0.

Otherwise, the result is not too bad. In fact, let’s see what happens if we interpret the expectation value not as four but as eight values.

steps = 24
blocks = 8
values = [simulate_step_noise(blocks, steps, x) for x in range(steps)]
print (values)
sorted(collections.Counter(values).items(), key=lambda x: x[0])
{'1': 45, '0': 955}
{'1': 71, '0': 929}
{'1': 132, '0': 868}
{'1': 134, '0': 866}
{'0': 811, '1': 189}
{'1': 202, '0': 798}
{'1': 266, '0': 734}
{'0': 700, '1': 300}
{'1': 325, '0': 675}
{'1': 368, '0': 632}
{'1': 413, '0': 587}
{'1': 440, '0': 560}
{'1': 486, '0': 514}
{'1': 530, '0': 470}
{'1': 580, '0': 420}
{'1': 623, '0': 377}
{'0': 354, '1': 646}
{'0': 301, '1': 699}
{'0': 275, '1': 725}
{'0': 226, '1': 774}
{'0': 213, '1': 787}
{'1': 825, '0': 175}
{'0': 121, '1': 879}
{'0': 97, '1': 903}
[0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7]
[(0, 2), (1, 4), (2, 4), (3, 3), (4, 3), (5, 3), (6, 3), (7, 2)]

We would expect to see each discretized value exactly three times. So, even though the results are not entirely unreasonable, we see a significant number of errors here.

So, discretized expectation values increase the susceptibility to noise. But, error mitigation techniques, such as the zero-noise extrapolation technique (ZNE) or Clifford Data Regression (CDR), help us mitigate the negative effect of noise.

Conclusion

Nevertheless, the ability to encode four or even eight instead of only two values could be a decisive factor when using current quantum computers. While we can’t easily increase the number of available qubits, we can use appropriate techniques, such as discretization. When combined with error mitigation techniques, we could possibly make our tiny number of qubits appear much bigger.

FOLLOW US ON GOOGLE NEWS

Read original article here

Denial of responsibility! Techno Blender is an automatic aggregator of the all world’s media. In each content, the hyperlink to the primary source is specified. All trademarks belong to their rightful owners, all materials to their authors. If you are the owner of the content and do not want us to publish your materials, please contact us by email – [email protected]. The content will be deleted within 24 hours.

Leave a comment