Diese Seite wurde von der Cloud Translation API übersetzt.
Switch to English

Gradienten berechnen

Ansicht auf TensorFlow.org In Google Colab ausführen Quelle auf GitHub anzeigen Notizbuch herunterladen

In diesem Tutorial werden Gradientenberechnungsalgorithmen für die Erwartungswerte von Quantenschaltungen untersucht.

Die Berechnung des Gradienten des Erwartungswerts einer bestimmten beobachtbaren Größe in einer Quantenschaltung ist ein komplizierter Prozess. Erwartungswerte von Observablen haben nicht den Luxus, analytische Gradientenformeln zu haben, die immer leicht aufzuschreiben sind - im Gegensatz zu herkömmlichen Transformationen des maschinellen Lernens wie Matrixmultiplikation oder Vektoraddition mit analytischen Gradientenformeln, die leicht aufzuschreiben sind. Infolgedessen gibt es verschiedene Methoden zur Berechnung von Quantengradienten, die für verschiedene Szenarien nützlich sind. In diesem Tutorial werden zwei verschiedene Differenzierungsschemata verglichen und gegenübergestellt.

Konfiguration

pip install -q tensorflow==2.3.1

Installieren Sie TensorFlow Quantum:

pip install -q tensorflow-quantum

Importieren Sie nun TensorFlow und die Modulabhängigkeiten:

import tensorflow as tf
import tensorflow_quantum as tfq

import cirq
import sympy
import numpy as np

# visualization tools
%matplotlib inline
import matplotlib.pyplot as plt
from cirq.contrib.svg import SVGCircuit

1. Vorläufig

Lassen Sie uns den Begriff der Gradientenberechnung für Quantenschaltungen etwas konkreter machen. Angenommen, Sie haben eine parametrisierte Schaltung wie diese:

qubit = cirq.GridQubit(0, 0)
my_circuit = cirq.Circuit(cirq.Y(qubit)**sympy.Symbol('alpha'))
SVGCircuit(my_circuit)
findfont: Font family ['Arial'] not found. Falling back to DejaVu Sans.

svg

Zusammen mit einem beobachtbaren:

pauli_x = cirq.X(qubit)
pauli_x
cirq.X(cirq.GridQubit(0, 0))

Wenn Sie sich diesen Operator ansehen, wissen Sie, dass $ ⟨Y (\ alpha) | X | Y (\ alpha)⟩ = \ sin (\ pi \ alpha) $

def my_expectation(op, alpha):
    """Compute ⟨Y(alpha)| `op` | Y(alpha)⟩"""
    params = {'alpha': alpha}
    sim = cirq.Simulator()
    final_state_vector = sim.simulate(my_circuit, params).final_state_vector
    return op.expectation_from_state_vector(final_state_vector, {qubit: 0}).real


my_alpha = 0.3
print("Expectation=", my_expectation(pauli_x, my_alpha))
print("Sin Formula=", np.sin(np.pi * my_alpha))
Expectation= 0.80901700258255
Sin Formula= 0.8090169943749475

und wenn Sie $ f_ {1} (\ alpha) = ⟨Y (\ alpha) | definieren X | Y (\ alpha)⟩ $ dann $ f_ {1} ^ {'} (\ alpha) = \ pi \ cos (\ pi \ alpha) $. Lassen Sie uns dies überprüfen:

def my_grad(obs, alpha, eps=0.01):
    grad = 0
    f_x = my_expectation(obs, alpha)
    f_x_prime = my_expectation(obs, alpha + eps)
    return ((f_x_prime - f_x) / eps).real


print('Finite difference:', my_grad(pauli_x, my_alpha))
print('Cosine formula:   ', np.pi * np.cos(np.pi * my_alpha))
Finite difference: 1.8063604831695557
Cosine formula:    1.8465818304904567

2. Die Notwendigkeit eines Unterscheidungsmerkmals

Bei größeren Schaltkreisen haben Sie nicht immer das Glück, eine Formel zu haben, die die Gradienten einer bestimmten Quantenschaltung genau berechnet. Für den Fall, dass eine einfache Formel nicht ausreicht, um den Gradienten zu berechnen, können Sie mit der Klasse tfq.differentiators.Differentiator Algorithmen zur Berechnung der Gradienten Ihrer Schaltungen definieren. Zum Beispiel können Sie das obige Beispiel in TensorFlow Quantum (TFQ) neu erstellen mit:

expectation_calculation = tfq.layers.Expectation(
    differentiator=tfq.differentiators.ForwardDifference(grid_spacing=0.01))

expectation_calculation(my_circuit,
                        operators=pauli_x,
                        symbol_names=['alpha'],
                        symbol_values=[[my_alpha]])
<tf.Tensor: shape=(1, 1), dtype=float32, numpy=array([[0.80901706]], dtype=float32)>

Wenn Sie jedoch zur Schätzung der Erwartung basierend auf der Stichprobe wechseln (was auf einem echten Gerät passieren würde), können sich die Werte geringfügig ändern. Dies bedeutet, dass Sie jetzt eine unvollständige Schätzung haben:

sampled_expectation_calculation = tfq.layers.SampledExpectation(
    differentiator=tfq.differentiators.ForwardDifference(grid_spacing=0.01))

sampled_expectation_calculation(my_circuit,
                                operators=pauli_x,
                                repetitions=500,
                                symbol_names=['alpha'],
                                symbol_values=[[my_alpha]])
<tf.Tensor: shape=(1, 1), dtype=float32, numpy=array([[0.784]], dtype=float32)>

Dies kann schnell zu einem ernsthaften Genauigkeitsproblem führen, wenn es um Farbverläufe geht:

# Make input_points = [batch_size, 1] array.
input_points = np.linspace(0, 5, 200)[:, np.newaxis].astype(np.float32)
exact_outputs = expectation_calculation(my_circuit,
                                        operators=pauli_x,
                                        symbol_names=['alpha'],
                                        symbol_values=input_points)
imperfect_outputs = sampled_expectation_calculation(my_circuit,
                                                    operators=pauli_x,
                                                    repetitions=500,
                                                    symbol_names=['alpha'],
                                                    symbol_values=input_points)
plt.title('Forward Pass Values')
plt.xlabel('$x$')
plt.ylabel('$f(x)$')
plt.plot(input_points, exact_outputs, label='Analytic')
plt.plot(input_points, imperfect_outputs, label='Sampled')
plt.legend()
<matplotlib.legend.Legend at 0x7ff6cfa8f668>

png

# Gradients are a much different story.
values_tensor = tf.convert_to_tensor(input_points)

with tf.GradientTape() as g:
    g.watch(values_tensor)
    exact_outputs = expectation_calculation(my_circuit,
                                            operators=pauli_x,
                                            symbol_names=['alpha'],
                                            symbol_values=values_tensor)
analytic_finite_diff_gradients = g.gradient(exact_outputs, values_tensor)

with tf.GradientTape() as g:
    g.watch(values_tensor)
    imperfect_outputs = sampled_expectation_calculation(
        my_circuit,
        operators=pauli_x,
        repetitions=500,
        symbol_names=['alpha'],
        symbol_values=values_tensor)
sampled_finite_diff_gradients = g.gradient(imperfect_outputs, values_tensor)

plt.title('Gradient Values')
plt.xlabel('$x$')
plt.ylabel('$f^{\'}(x)$')
plt.plot(input_points, analytic_finite_diff_gradients, label='Analytic')
plt.plot(input_points, sampled_finite_diff_gradients, label='Sampled')
plt.legend()
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/util/deprecation.py:574: calling map_fn_v2 (from tensorflow.python.ops.map_fn) with dtype is deprecated and will be removed in a future version.
Instructions for updating:
Use fn_output_signature instead

<matplotlib.legend.Legend at 0x7ff5c82e0780>

png

Hier können Sie sehen, dass die Finite-Differenzen-Formel zwar schnell ist, um die Gradienten selbst im analytischen Fall zu berechnen, aber bei den stichprobenbasierten Methoden viel zu verrauscht war. Es müssen vorsichtigere Techniken angewendet werden, um sicherzustellen, dass ein guter Gradient berechnet werden kann. Als nächstes werden Sie sich eine viel langsamere Technik ansehen, die für analytische Erwartungsgradientenberechnungen nicht so gut geeignet wäre, aber im realen stichprobenbasierten Fall eine viel bessere Leistung erbringt:

# A smarter differentiation scheme.
gradient_safe_sampled_expectation = tfq.layers.SampledExpectation(
    differentiator=tfq.differentiators.ParameterShift())

with tf.GradientTape() as g:
    g.watch(values_tensor)
    imperfect_outputs = gradient_safe_sampled_expectation(
        my_circuit,
        operators=pauli_x,
        repetitions=500,
        symbol_names=['alpha'],
        symbol_values=values_tensor)

sampled_param_shift_gradients = g.gradient(imperfect_outputs, values_tensor)

plt.title('Gradient Values')
plt.xlabel('$x$')
plt.ylabel('$f^{\'}(x)$')
plt.plot(input_points, analytic_finite_diff_gradients, label='Analytic')
plt.plot(input_points, sampled_param_shift_gradients, label='Sampled')
plt.legend()
<matplotlib.legend.Legend at 0x7ff6cf834860>

png

Aus dem Obigen können Sie ersehen, dass bestimmte Unterscheidungsmerkmale für bestimmte Forschungsszenarien am besten verwendet werden. Im Allgemeinen sind die langsameren stichprobenbasierten Methoden, die gegenüber Geräuschen von Geräten usw. robust sind, ein großes Unterscheidungsmerkmal beim Testen oder Implementieren von Algorithmen in einer "realeren" Umgebung. Schnellere Methoden wie die endliche Differenz eignen sich hervorragend für analytische Berechnungen, und Sie möchten einen höheren Durchsatz, sind jedoch noch nicht mit der Gerätelebensfähigkeit Ihres Algorithmus befasst.

3. Mehrere Observablen

Lassen Sie uns eine zweite Observable vorstellen und sehen, wie TensorFlow Quantum mehrere Observable für eine einzelne Schaltung unterstützt.

pauli_z = cirq.Z(qubit)
pauli_z
cirq.Z(cirq.GridQubit(0, 0))

Wenn dieses Observable mit derselben Schaltung wie zuvor verwendet wird, haben Sie $ f_ {2} (\ alpha) = ⟨Y (\ alpha) | Z | Y (\ alpha)⟩ = \ cos (\ pi \ alpha) $ und $ f_ {2} ^ {'} (\ alpha) = - \ pi \ sin (\ pi \ alpha) $. Führen Sie eine schnelle Überprüfung durch:

test_value = 0.

print('Finite difference:', my_grad(pauli_z, test_value))
print('Sin formula:      ', -np.pi * np.sin(np.pi * test_value))
Finite difference: -0.04934072494506836
Sin formula:       -0.0

Es ist ein Match (nah genug).

Wenn Sie nun $ g (\ alpha) = f_ {1} (\ alpha) + f_ {2} (\ alpha) $ definieren, dann $ g '(\ alpha) = f_ {1} ^ {'} (\ alpha) + f ^ {'} _ {2} (\ alpha) $. Das Definieren von mehr als einem in TensorFlow Quantum beobachtbaren Element zur Verwendung zusammen mit einer Schaltung entspricht dem Hinzufügen weiterer Begriffe zu $ ​​g $.

Dies bedeutet, dass der Gradient eines bestimmten Symbols in einer Schaltung gleich der Summe der Gradienten in Bezug auf jedes für dieses Symbol beobachtbare Symbol ist, das auf diese Schaltung angewendet wird. Dies ist kompatibel mit TensorFlow-Gradientenaufnahme und Backpropagation (wobei Sie die Summe der Gradienten über alle Observablen als Gradienten für ein bestimmtes Symbol angeben).

sum_of_outputs = tfq.layers.Expectation(
    differentiator=tfq.differentiators.ForwardDifference(grid_spacing=0.01))

sum_of_outputs(my_circuit,
               operators=[pauli_x, pauli_z],
               symbol_names=['alpha'],
               symbol_values=[[test_value]])
<tf.Tensor: shape=(1, 2), dtype=float32, numpy=array([[1.9106855e-15, 1.0000000e+00]], dtype=float32)>

Hier sehen Sie, dass der erste Eintrag die Erwartung für Pauli X und der zweite die Erwartung für Pauli Z ist. Wenn Sie nun den Gradienten nehmen:

test_value_tensor = tf.convert_to_tensor([[test_value]])

with tf.GradientTape() as g:
    g.watch(test_value_tensor)
    outputs = sum_of_outputs(my_circuit,
                             operators=[pauli_x, pauli_z],
                             symbol_names=['alpha'],
                             symbol_values=test_value_tensor)

sum_of_gradients = g.gradient(outputs, test_value_tensor)

print(my_grad(pauli_x, test_value) + my_grad(pauli_z, test_value))
print(sum_of_gradients.numpy())
3.0917350202798843
[[3.0917213]]

Hier haben Sie überprüft, dass die Summe der Gradienten für jedes beobachtbare Element tatsächlich der Gradient von $ \ alpha $ ist. Dieses Verhalten wird von allen TensorFlow Quantum-Unterscheidungsmerkmalen unterstützt und spielt eine entscheidende Rolle für die Kompatibilität mit dem Rest von TensorFlow.

4. Erweiterte Verwendung

Hier erfahren Sie, wie Sie Ihre eigenen benutzerdefinierten Differenzierungsroutinen für Quantenschaltungen definieren. Alle Unterscheidungsmerkmale, die innerhalb der TensorFlow Quantum-Unterklasse tfq.differentiators.Differentiator . Ein Differenzierer muss differentiate_analytic und differentiate_sampled implementieren.

Im Folgenden werden TensorFlow Quantum-Konstrukte verwendet, um die geschlossene Formlösung aus dem ersten Teil dieses Lernprogramms zu implementieren.

class MyDifferentiator(tfq.differentiators.Differentiator):
    """A Toy differentiator for <Y^alpha | X |Y^alpha>."""

    def __init__(self):
        pass

    @tf.function
    def _compute_gradient(self, symbol_values):
        """Compute the gradient based on symbol_values."""

        # f(x) = sin(pi * x)
        # f'(x) = pi * cos(pi * x)
        return tf.cast(tf.cos(symbol_values * np.pi) * np.pi, tf.float32)

    @tf.function
    def differentiate_analytic(self, programs, symbol_names, symbol_values,
                               pauli_sums, forward_pass_vals, grad):
        """Specify how to differentiate a circuit with analytical expectation.

        This is called at graph runtime by TensorFlow. `differentiate_analytic`
        should calculate the gradient of a batch of circuits and return it
        formatted as indicated below. See
        `tfq.differentiators.ForwardDifference` for an example.

        Args:
            programs: `tf.Tensor` of strings with shape [batch_size] containing
                the string representations of the circuits to be executed.
            symbol_names: `tf.Tensor` of strings with shape [n_params], which
                is used to specify the order in which the values in
                `symbol_values` should be placed inside of the circuits in
                `programs`.
            symbol_values: `tf.Tensor` of real numbers with shape
                [batch_size, n_params] specifying parameter values to resolve
                into the circuits specified by programs, following the ordering
                dictated by `symbol_names`.
            pauli_sums: `tf.Tensor` of strings with shape [batch_size, n_ops]
                containing the string representation of the operators that will
                be used on all of the circuits in the expectation calculations.
            forward_pass_vals: `tf.Tensor` of real numbers with shape
                [batch_size, n_ops] containing the output of the forward pass
                through the op you are differentiating.
            grad: `tf.Tensor` of real numbers with shape [batch_size, n_ops]
                representing the gradient backpropagated to the output of the
                op you are differentiating through.

        Returns:
            A `tf.Tensor` with the same shape as `symbol_values` representing
            the gradient backpropagated to the `symbol_values` input of the op
            you are differentiating through.
        """

        # Computing gradients just based off of symbol_values.
        return self._compute_gradient(symbol_values) * grad

    @tf.function
    def differentiate_sampled(self, programs, symbol_names, symbol_values,
                              pauli_sums, num_samples, forward_pass_vals, grad):
        """Specify how to differentiate a circuit with sampled expectation.

        This is called at graph runtime by TensorFlow. `differentiate_sampled`
        should calculate the gradient of a batch of circuits and return it
        formatted as indicated below. See
        `tfq.differentiators.ForwardDifference` for an example.

        Args:
            programs: `tf.Tensor` of strings with shape [batch_size] containing
                the string representations of the circuits to be executed.
            symbol_names: `tf.Tensor` of strings with shape [n_params], which
                is used to specify the order in which the values in
                `symbol_values` should be placed inside of the circuits in
                `programs`.
            symbol_values: `tf.Tensor` of real numbers with shape
                [batch_size, n_params] specifying parameter values to resolve
                into the circuits specified by programs, following the ordering
                dictated by `symbol_names`.
            pauli_sums: `tf.Tensor` of strings with shape [batch_size, n_ops]
                containing the string representation of the operators that will
                be used on all of the circuits in the expectation calculations.
            num_samples: `tf.Tensor` of positive integers representing the
                number of samples per term in each term of pauli_sums used
                during the forward pass.
            forward_pass_vals: `tf.Tensor` of real numbers with shape
                [batch_size, n_ops] containing the output of the forward pass
                through the op you are differentiating.
            grad: `tf.Tensor` of real numbers with shape [batch_size, n_ops]
                representing the gradient backpropagated to the output of the
                op you are differentiating through.

        Returns:
            A `tf.Tensor` with the same shape as `symbol_values` representing
            the gradient backpropagated to the `symbol_values` input of the op
            you are differentiating through.
        """
        return self._compute_gradient(symbol_values) * grad

Dieses neue Unterscheidungsmerkmal kann jetzt mit vorhandenen tfq.layer Objekten verwendet werden:

custom_dif = MyDifferentiator()
custom_grad_expectation = tfq.layers.Expectation(differentiator=custom_dif)

# Now let's get the gradients with finite diff.
with tf.GradientTape() as g:
    g.watch(values_tensor)
    exact_outputs = expectation_calculation(my_circuit,
                                            operators=[pauli_x],
                                            symbol_names=['alpha'],
                                            symbol_values=values_tensor)

analytic_finite_diff_gradients = g.gradient(exact_outputs, values_tensor)

# Now let's get the gradients with custom diff.
with tf.GradientTape() as g:
    g.watch(values_tensor)
    my_outputs = custom_grad_expectation(my_circuit,
                                         operators=[pauli_x],
                                         symbol_names=['alpha'],
                                         symbol_values=values_tensor)

my_gradients = g.gradient(my_outputs, values_tensor)

plt.subplot(1, 2, 1)
plt.title('Exact Gradient')
plt.plot(input_points, analytic_finite_diff_gradients.numpy())
plt.xlabel('x')
plt.ylabel('f(x)')
plt.subplot(1, 2, 2)
plt.title('My Gradient')
plt.plot(input_points, my_gradients.numpy())
plt.xlabel('x')
Text(0.5, 0, 'x')

png

Dieses neue Unterscheidungsmerkmal kann jetzt verwendet werden, um differenzierbare Operationen zu generieren.

# Create a noisy sample based expectation op.
expectation_sampled = tfq.get_sampled_expectation_op(
    cirq.DensityMatrixSimulator(noise=cirq.depolarize(0.01)))

# Make it differentiable with your differentiator:
# Remember to refresh the differentiator before attaching the new op
custom_dif.refresh()
differentiable_op = custom_dif.generate_differentiable_op(
    sampled_op=expectation_sampled)

# Prep op inputs.
circuit_tensor = tfq.convert_to_tensor([my_circuit])
op_tensor = tfq.convert_to_tensor([[pauli_x]])
single_value = tf.convert_to_tensor([[my_alpha]])
num_samples_tensor = tf.convert_to_tensor([[1000]])

with tf.GradientTape() as g:
    g.watch(single_value)
    forward_output = differentiable_op(circuit_tensor, ['alpha'], single_value,
                                       op_tensor, num_samples_tensor)

my_gradients = g.gradient(forward_output, single_value)

print('---TFQ---')
print('Foward:  ', forward_output.numpy())
print('Gradient:', my_gradients.numpy())
print('---Original---')
print('Forward: ', my_expectation(pauli_x, my_alpha))
print('Gradient:', my_grad(pauli_x, my_alpha))
---TFQ---
Foward:   [[0.774]]
Gradient: [[1.8465817]]
---Original---
Forward:  0.80901700258255
Gradient: 1.8063604831695557