Italian
Linguaggi
English
Bengali
French
Hindi
Italian
Japanese
Korean
Malayalam
Russian
Spanish
Tamil
Turkish
Vietnamese
Shortcuts

Nota

Questa pagina è stata generata da docs/tutorials/01_neural_networks.ipynb.

Quantum Neural Network (Reti Neurali Quantistiche)

Questo notebook mostra le differenti implementazioni di una generica quantum neural network (QNN) presenti in Qiskit Machine Learning. Le reti sono intese come unità computazionali agnostiche rispetto alla loro applicazione e possono quindi essere utilizzate in diversi casi d’uso. In base all’applicazione che si sta considerando, una specifica rete può essere più o meno adatta e può richiedere di essere impostata in un modo specifico. Discuteremo più nel dettaglio le seguenti diverse reti neurali:

  1. NeuralNetwork: L’interfaccia per le reti neurali.

  2. OpflowQNN: Una rete basata sulla valutazione degli osservabili di meccanica quantistica.

  3. TwoLayerQNN: Una speciale implementazione di OpflowQNN, implementata perchè spesso utilizzata.

  4. CircuitQNN: Una rete basata sui campioni risultanti dalla misurazione di un circuito quantistico.

[1]:
import numpy as np

from qiskit import Aer, QuantumCircuit
from qiskit.circuit import Parameter
from qiskit.circuit.library import RealAmplitudes, ZZFeatureMap
from qiskit.opflow import StateFn, PauliSumOp, AerPauliExpectation, ListOp, Gradient
from qiskit.utils import QuantumInstance
[2]:
# set method to calculcate expected values
expval = AerPauliExpectation()

# define gradient method
gradient = Gradient()

# define quantum instances (statevector and sample based)
qi_sv = QuantumInstance(Aer.get_backend('aer_simulator_statevector'))

# we set shots to 10 as this will determine the number of samples later on.
qi_qasm = QuantumInstance(Aer.get_backend('aer_simulator'), shots=10)

1. NeuralNetwork

NeuralNetwork rappresenta l’interfaccia per tutte le reti neurali disponibili in Qiskit Machine Learning. Espone un forward pass ed un backward pass, prendendo come input campioni di dati e pesi addestrabili. Una NeuralNetwork non contiene alcuna capacità di allenamento, questa viene effettuata dagli algoritmi / applicazioni effettivamente utilizzati. Quindi, una NeuralNetwork non memorizza automaticamente i valori dei pesi addestrabili. Qui di seguito sono introdotte differenti implementazioni di questa interfaccia.

Supponiamo di avere una NeuralNetwork chiamata nn. Allora, il comando pass nn.forward(input, weights) accetta sia un input flat per i dati che i pesi, di dimensione rispettivamente nn.num_inputs e nn.num_weights. NeuralNetwork supporta processi batch per gli input, restituendo batch di output delle dimensioni corrispondenti.

2. OpflowQNN

OpflowQNN accetta in input un operatore (parametrizzato) da Qiskit e sfrutta il framework di gradienti di Qiskit per eseguire il backward pass. Per esempio, tale operatore può essere un valore di aspettazione di un osservabile quantistico in relazione ad uno stato quantistico parametrizzato. I Parameters possono essere usati per caricare dei dati classici, come anche possono essere usati per rappresentare i pesi addestrabili. OpflowQNN accetta anche liste di operatori e strutture più complesse per costruire delle QNN più complesse.

[3]:
from qiskit_machine_learning.neural_networks import OpflowQNN
[4]:
# construct parametrized circuit
params1 = [Parameter('input1'), Parameter('weight1')]
qc1 = QuantumCircuit(1)
qc1.h(0)
qc1.ry(params1[0], 0)
qc1.rx(params1[1], 0)
qc_sfn1 = StateFn(qc1)

# construct cost operator
H1 = StateFn(PauliSumOp.from_list([('Z', 1.0), ('X', 1.0)]))

# combine operator and circuit to objective function
op1 = ~H1 @ qc_sfn1
print(op1)
ComposedOp([
  OperatorMeasurement(1.0 * Z
  + 1.0 * X),
  CircuitStateFn(
       ┌───┐┌────────────┐┌─────────────┐
  q_0: ┤ H ├┤ RY(input1) ├┤ RX(weight1) ├
       └───┘└────────────┘└─────────────┘
  )
])
[5]:
# construct OpflowQNN with the operator, the input parameters, the weight parameters,
# the expected value, gradient, and quantum instance.
qnn1 = OpflowQNN(op1, [params1[0]], [params1[1]], expval, gradient, qi_sv)
[6]:
# define (random) input and weights
input1 = np.random.rand(qnn1.num_inputs)
weights1 = np.random.rand(qnn1.num_weights)
[7]:
# QNN forward pass
qnn1.forward(input1, weights1)
[7]:
array([[0.81422018]])
[8]:
# QNN batched forward pass
qnn1.forward([input1, input1], weights1)
[8]:
array([[0.81422018],
       [0.81422018]])
[9]:
# QNN backward pass
qnn1.backward(input1, weights1)
[9]:
(array([[[-1.15629599]]]), array([[[0.0008565]]]))
[10]:
# QNN batched backward pass
qnn1.backward([input1, input1], weights1)
[10]:
(array([[[-1.15629599]],

        [[-1.15629599]]]),
 array([[[0.0008565]],

        [[0.0008565]]]))

La combinazione di più osservabili in una ListOp permette anche di creare QNN più complesse

[11]:
op2 = ListOp([op1, op1])
qnn2 = OpflowQNN(op2, [params1[0]], [params1[1]], expval, gradient, qi_sv)
[12]:
# QNN forward pass
qnn2.forward(input1, weights1)
[12]:
array([[0.81422018, 0.81422018]])
[13]:
# QNN backward pass
qnn2.backward(input1, weights1)
[13]:
(array([[[-1.15629599],
         [-1.15629599]]]),
 array([[[0.0008565],
         [0.0008565]]]))

3. TwoLayerQNN

La TwoLayerQNN è un caso particolare di OpflowQNN che agisce su \(n\) qubit e che è composta prima da una feature map per inserire i dati, poi da un ansatz che viene addestrato. L’osservabile di default è \(Z^{\otimes n}\), ovvero la parità.

[14]:
from qiskit_machine_learning.neural_networks import TwoLayerQNN
[15]:
# specify the number of qubits
num_qubits = 3
[16]:
# specify the feature map
fm = ZZFeatureMap(num_qubits, reps=2)
fm.draw(output='mpl')
[16]:
../_images/tutorials_01_neural_networks_20_0.png
[17]:
# specify the ansatz
ansatz = RealAmplitudes(num_qubits, reps=1)
ansatz.draw(output='mpl')
[17]:
../_images/tutorials_01_neural_networks_21_0.png
[18]:
# specify the observable
observable = PauliSumOp.from_list([('Z'*num_qubits, 1)])
print(observable)
1.0 * ZZZ
[19]:
# define two layer QNN
qnn3 = TwoLayerQNN(num_qubits,
                   feature_map=fm,
                   ansatz=ansatz,
                   observable=observable, quantum_instance=qi_sv)
[20]:
# define (random) input and weights
input3 = np.random.rand(qnn3.num_inputs)
weights3 = np.random.rand(qnn3.num_weights)
[21]:
# QNN forward pass
qnn3.forward(input3, weights3)
[21]:
array([[0.28520667]])
[22]:
# QNN backward pass
qnn3.backward(input3, weights3)
[22]:
(array([[[-0.2933431 , -0.78565729,  0.5416021 ]]]),
 array([[[-0.09093077,  0.02802006, -0.13559047,  0.1814619 ,
           0.11644461, -0.4073129 ]]]))

4. CircuitQNN

CircuitQNN si basa su un QuantumCircuit (parametrizzato). Questo accetta parametri di input, come anche parametri dei pesi, per poi produrre campioni dalla misurazione. I campioni possono essere interpretati sia come la probabilità di misurare l’indice intero corrispondente ad una bitstring, sia direttamente come un batch di output binario. Nel caso delle probabilità, i gradienti possono essere stimati in modo efficiente ed il CircuitQNN fornisce anche un backward pass. In caso di campioni, la derivazione non è possibile, ed il backward pass restituisce (None, None).

Inoltre, il CircuitQNN permette di specificare una funzione interpret per effettuare il post processamento dei campioni. Ci si aspetta che questo modulo mappi un intero misurato (da una bitstring) in un nuovo indice, ad esempio un indice non negativo. In questo caso, è necessario specificare la dimensione dell’output e le probabilità sono aggregate di conseguenza.

Un CircuitQNN può essere configurato per restituire vettori di probabilità sia sparsi che densi. Se non viene utilizzata alcuna funzione interpret, la dimensione del vettore di probabilità cresce in modo esponenziale con il numero di qubit, ed è spesso suggerito l’utilizzo di una configurazione sparsa. Nel caso in cui venga specificata una funzione interpret, la situazione dipende dall’esito previsto. Se, per esempio, un indice viene mappato alla parità della corrispondente bitstring, e.g. a 0 o 1, ha senso un output denso ed il risultato sarà un vettore di probabilità di lunghezza 2.

[23]:
from qiskit_machine_learning.neural_networks import CircuitQNN
[24]:
qc = RealAmplitudes(num_qubits, entanglement='linear', reps=1)
qc.draw(output='mpl')
[24]:
../_images/tutorials_01_neural_networks_29_0.png

4.1 Output: probabilità sparse intere

[25]:
# specify circuit QNN
qnn4 = CircuitQNN(qc, [], qc.parameters, sparse=True, quantum_instance=qi_qasm)
[26]:
# define (random) input and weights
input4 = np.random.rand(qnn4.num_inputs)
weights4 = np.random.rand(qnn4.num_weights)
[27]:
# QNN forward pass
qnn4.forward(input4, weights4).todense()  # returned as a sparse matrix
[27]:
array([[0.5, 0. , 0. , 0. , 0.3, 0. , 0. , 0.2]])
[28]:
# QNN backward pass, returns a tuple of sparse matrices
qnn4.backward(input4, weights4)
[28]:
(<COO: shape=(1, 8, 0), dtype=float64, nnz=0, fill_value=0.0>,
 <COO: shape=(1, 8, 6), dtype=float64, nnz=22, fill_value=0.0>)

4.2 Output: probabilità di parità dense

[29]:
# specify circuit QNN
parity = lambda x: '{:b}'.format(x).count('1') % 2
output_shape = 2  # this is required in case of a callable with dense output
qnn6 = CircuitQNN(qc, [], qc.parameters, sparse=False, interpret=parity, output_shape=output_shape,
                  quantum_instance=qi_qasm)
[30]:
# define (random) input and weights
input6 = np.random.rand(qnn6.num_inputs)
weights6 = np.random.rand(qnn6.num_weights)
[31]:
# QNN forward pass
qnn6.forward(input6, weights6)
[31]:
array([[0.8, 0.2]])
[32]:
# QNN backward pass
qnn6.backward(input6, weights6)
[32]:
(array([], shape=(1, 2, 0), dtype=float64),
 array([[[-1.00000000e-01,  5.55111512e-17, -5.00000000e-02,
           1.38777878e-17, -2.00000000e-01,  5.00000000e-02],
         [ 1.00000000e-01,  0.00000000e+00,  5.00000000e-02,
          -5.55111512e-17,  2.00000000e-01, -5.00000000e-02]]]))

4.3 Output: Campioni

[33]:
# specify circuit QNN
qnn7 = CircuitQNN(qc, [], qc.parameters, sampling=True,
                  quantum_instance=qi_qasm)
[34]:
# define (random) input and weights
input7 = np.random.rand(qnn7.num_inputs)
weights7 = np.random.rand(qnn7.num_weights)
[35]:
# QNN forward pass, results in samples of measured bit strings mapped to integers
qnn7.forward(input7, weights7)
[35]:
array([[[4.],
        [4.],
        [7.],
        [1.],
        [7.],
        [4.],
        [4.],
        [4.],
        [0.],
        [0.]]])
[36]:
# QNN backward pass
qnn7.backward(input7, weights7)
[36]:
(None, None)

4.4 Output: Campioni di parità

[37]:
# specify circuit QNN
qnn8 = CircuitQNN(qc, [], qc.parameters, sampling=True, interpret=parity,
                  quantum_instance=qi_qasm)
[38]:
# define (random) input and weights
input8 = np.random.rand(qnn8.num_inputs)
weights8 = np.random.rand(qnn8.num_weights)
[39]:
# QNN forward pass, results in samples of measured bit strings
qnn8.forward(input8, weights8)
[39]:
array([[[1.],
        [0.],
        [1.],
        [0.],
        [0.],
        [0.],
        [0.],
        [0.],
        [0.],
        [0.]]])
[40]:
# QNN backward pass
qnn8.backward(input8, weights8)
[40]:
(None, None)
[41]:
import qiskit.tools.jupyter
%qiskit_version_table
%qiskit_copyright

Version Information

Qiskit SoftwareVersion
QiskitNone
Terra0.17.0.dev0+346ffa8
Aer0.8.0
Ignis0.6.0.dev0+d6f1ad7
AquaNone
IBM Q Provider0.13.0.dev0+10f19e0
System information
Python3.8.8 (default, Feb 24 2021, 13:46:16) [Clang 10.0.0 ]
OSDarwin
CPUs6
Memory (Gb)16.0
Wed Mar 31 23:25:48 2021 CEST

This code is a part of Qiskit

© Copyright IBM 2017, 2021.

This code is licensed under the Apache License, Version 2.0. You may
obtain a copy of this license in the LICENSE.txt file in the root directory
of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.

Any modifications or derivative works of this code must retain this
copyright notice, and modified files need to carry a notice indicating
that they have been altered from the originals.

[ ]: