Neural networks is one of the major branches in machine learning, with wide use in applications and research. A neural network—or, more generally, a deep neural network—is a parametric function of a specific structure (inspired by neural networks in biology), which is trained to capture specific functionality.In its most basic form, a neural network for learning a function f:RN→RM looks as follows:
There is an input vector of size N (red circles in Fig. 1).
Each entry of the input goes into a hidden layer of size K, where each neuron (blue circles in Fig. 1) is defined with an “activation function” yk(w(1);x) for k=1,…,K, and w(1) are parameters.
The output of the hidden layer is sent to the output layer (green circles in Fig. 1) f~m(w(2);y) for m=1,…,M, and w(2) are parameters.
The output f~ is thus a parametric function (in w(1),w(2)), which can be trained to capture the target function f.
Deep neural networks are similar to the description above, having more than one hidden layer.This provides a more complex structure that can capture more complex functionalities.
The idea of a quantum neural network refers to combining parametric circuits as a replacement for all or some of the classical layers in classical neural networks.The basic object in QNN is thus a quantum layer, which has a classical input and returns a classical output.The output is obtained by running a quantum program. A quantum layer is thus composed of three parts:
A quantum part that encodes the input: This is a parametric quantum function for representing the entries of a single data point.
There are three canonical ways to encode a data vector of size N: angle-encoding using N qubits, dense angle-encoding using ⌈N/2⌉ qubits, and amplitude-encoding using ⌈log2N⌉ qubits.
A quantum ansatz part: This is a parametric quantum function, whose parameters are trained as the weights in classical layers.
A postprocess classical part, for returning an output classical vector.
The integration of quantum layers in classical neural networks may offer reduction in resources for a given functionality, as the network (or part of it) is expressed via the Hilbert space, providing different expressibility compared to classical networks.This notebook demonstrates QNN by treating a specific function—the subset majority—for which we construct, train, and verify a hybrid classical-quantum neural network.The notebook assumes familiarity with Classiq and NN with PyTorch.See the QML guide with Classiq.
Example: Hybrid Neural Network for the Subset Majority Function
For an integer N and a given subset of indices S⊂{0,1,…,N} we define the subset majority function, MS:{0,1}×N→{0,1} that acts on binary strings of size N as follows: it returns 1 if the number of ones within the substring according to S is larger than ∣S∣//2, and 0 otherwise,MS(b)={10if ∑j∈Sbj>∣S∣//2,otherwiseFor example, we consider N=7 and S={0,1,4}:
The string 0101110 corresponds to the substring 011, for which the number of ones is 2(>1). Therefore, MS(0101110)=1.
The string 0011111 corresponds to the substring 001, for which the number of ones is 1(=1). Therefore, MS(0101110)=0.
Let us consider a specific example for our demonstration. We choose N=10 and generate all possible data of 2N bit strings. We also take a specific subset S={1,3,4,6,7,9}.
!pip install -qq -U "classiq[qml]"
import randomimport numpy as npnp.random.seed(0)random.seed(1)STRING_LEN = 10majority_data = [ [int(d) for d in np.binary_repr(k, STRING_LEN)] for k in range(2**STRING_LEN)]random.shuffle(majority_data) # shuffling the data
We build the following hybrid neural network:Data flattening → A classical linear layer of size 10 to 4 with Tanh activation → A qlayer of size 4 to 2 → a classical linear layer of size 2 to 1 with ReLU activation.The classical layers can be defined with PyTorch built-in functions.The quantum layer is constructed with(1) a dense angle-encoding function(2) a simple ansatz with RY and RZZ rotations(3) a postprocess that is based on a measurement per qubit
from classiq import *from classiq.applications.qnn.types import SavedResultfrom classiq.execution import ExecutionPreferences, execute_qnn@qfuncdef my_ansatz(weights: CArray[CReal], qbv: QArray) -> None: """ Gets a quantum variable of $m$ qubits, and applies RY gate on each qubit and RZZ gate on each pair of qubits in a linear connectivity.The classical array weights represents the $2m-1$ parametric rotations. """ repeat( count=qbv.len, iteration=lambda index: RY(weights[index], qbv[index]), ) repeat( count=qbv.len - 1, iteration=lambda index: RZZ(weights[qbv.len + index], qbv[index : index + 2]), )
QLAYER_SIZE = 4num_qubits = int(np.ceil(QLAYER_SIZE / 2))num_weights = 2 * num_qubits - 1NUM_SHOTS = 4096@qfuncdef main( input: CArray[CReal, QLAYER_SIZE], weight: CArray[CReal, num_weights], result: Output[QArray],) -> None: """ The quantum part of the quantum layer.The prefix for the data loading parameters must be set to `input_` or `i_`.The prefix for the ansatz parameters must be set to `weights_` or `weight` """ encode_on_bloch(input, result) my_ansatz(weights=weight, qbv=result)qmod = create_model( main, execution_preferences=ExecutionPreferences(num_shots=NUM_SHOTS))qprog = synthesize(qmod)show(qprog)
Output:
Quantum program link: https://platform.classiq.io/circuit/32pQqOSulTv1Qvssd71mPgf4btU
def my_post_process(result: SavedResult, num_qubits, num_shots) -> torch.Tensor: """ Classical postprocess function.Gets the histogram after execution and returns a vector $\vec{y}$, where $y_i$ is the probability of measuring 1 on the $i$-th qubit. """ res = result.value yvec = [ (res.counts_of_qubits(k)["1"] if "1" in res.counts_of_qubits(k) else 0) / num_shots for k in range(num_qubits) ] return torch.tensor(yvec)