Zapisz datę! Google I / O powraca w dniach 18-20 maja Zarejestruj się teraz
Ta strona została przetłumaczona przez Cloud Translation API.
Switch to English

Oblicz gradienty

Zobacz na TensorFlow.org Uruchom w Google Colab Wyświetl źródło w serwisie GitHub Pobierz notatnik

Ten samouczek bada algorytmy obliczania gradientu dla wartości oczekiwanych obwodów kwantowych.

Obliczanie gradientu wartości oczekiwanej pewnej obserwowalnej w obwodzie kwantowym jest procesem złożonym. Wartości oczekiwane obserwabli nie mają luksusu posiadania analitycznych wzorów gradientowych, które są zawsze łatwe do zapisania - w przeciwieństwie do tradycyjnych przekształceń maszynowych, takich jak mnożenie macierzy lub dodawanie wektorów, które mają analityczne wzory gradientowe, które są łatwe do zapisania. W rezultacie istnieją różne metody obliczania gradientu kwantowego, które są przydatne w różnych scenariuszach. Ten samouczek porównuje i porównuje dwa różne schematy różnicowania.

Ustawiać

pip install -q tensorflow==2.3.1

Zainstaluj TensorFlow Quantum:

pip install -q tensorflow-quantum

Teraz zaimportuj TensorFlow i zależności modułów:

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. Wstępne

Niech pojęcie obliczania gradientu dla obwodów kwantowych będzie trochę bardziej konkretne. Załóżmy, że masz sparametryzowany obwód, taki jak ten:

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

Wraz z obserwowalnym:

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

Patrząc na ten operator, wiesz, że $ ⟨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

i jeśli zdefiniujesz $ f_ {1} (\ alpha) = ⟨Y (\ alpha) | X | Y (\ alpha)⟩ $, a następnie $ f_ {1} ^ {'} (\ alpha) = \ pi \ cos (\ pi \ alpha) $. Sprawdźmy to:

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. Potrzeba wyróżnienia

W przypadku większych obwodów nie zawsze będziesz miał tyle szczęścia, że ​​masz wzór, który precyzyjnie oblicza gradienty danego obwodu kwantowego. W przypadku, gdy prosta formuła nie wystarczy do obliczenia gradientu, klasa tfq.differentiators.Differentiator umożliwia zdefiniowanie algorytmów obliczania gradientów twoich obwodów. Na przykład możesz odtworzyć powyższy przykład w TensorFlow Quantum (TFQ) za pomocą:

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)>

Jeśli jednak przełączysz się na szacowanie oczekiwań na podstawie próbkowania (co by się stało na prawdziwym urządzeniu), wartości mogą się nieco zmienić. Oznacza to, że masz teraz niedoskonałe oszacowanie:

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)>

Może to szybko przerodzić się w poważny problem z dokładnością, jeśli chodzi o gradienty:

# 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 0x7f88a03f3320>

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 0x7f8899609a58>

png

Tutaj widać, że chociaż wzór na różnicę skończoną jest szybki w obliczaniu samych gradientów w przypadku analitycznym, w przypadku metod opartych na próbkowaniu był on zbyt głośny. Aby obliczyć dobry gradient, należy zastosować bardziej ostrożne techniki. Następnie przyjrzymy się znacznie wolniejszej technice, która nie nadawałaby się tak dobrze do analitycznych obliczeń gradientu oczekiwań, ale działa znacznie lepiej w przypadku opartym na próbkach w świecie rzeczywistym:

# 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 0x7f889dcddf60>

png

Z powyższego widać, że pewne wyróżniki najlepiej nadają się do określonych scenariuszy badawczych. Ogólnie rzecz biorąc, wolniejsze metody oparte na próbkach, które są odporne na szumy urządzenia itp., Są świetnymi czynnikami różnicującymi podczas testowania lub implementowania algorytmów w bardziej „rzeczywistym świecie”. Szybsze metody, takie jak różnica skończona, świetnie nadają się do obliczeń analitycznych i chcesz uzyskać wyższą przepustowość, ale nie jesteś jeszcze zainteresowany wykonalnością urządzenia swojego algorytmu.

3. Wiele obserwacji

Przedstawmy drugą obserwowalną i zobaczmy, jak TensorFlow Quantum obsługuje wiele obserwabli dla pojedynczego obwodu.

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

Jeśli ta obserwowalna jest używana z tym samym obwodem co poprzednio, masz $ f_ {2} (\ alpha) = ⟨Y (\ alpha) | Z | Y (\ alpha)⟩ = \ cos (\ pi \ alpha) $ i $ f_ {2} ^ {'} (\ alpha) = - \ pi \ sin (\ pi \ alpha) $. Wykonaj szybką kontrolę:

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

To dopasowanie (wystarczająco blisko).

Teraz, jeśli zdefiniujesz $ g (\ alpha) = f_ {1} (\ alpha) + f_ {2} (\ alpha) $ then $ g '(\ alpha) = f_ {1} ^ {'} (\ alpha) + f ^ {'} _ {2} (\ alpha) $. Zdefiniowanie więcej niż jednego obserwowalnego w TensorFlow Quantum do użycia wraz z obwodem jest równoznaczne z dodaniem większej liczby terminów do $ g $.

Oznacza to, że gradient określonego symbolu w obwodzie jest równy sumie gradientów w odniesieniu do każdego obserwowalnego dla tego symbolu zastosowanego do tego obwodu. Jest to kompatybilne z pobieraniem gradientów TensorFlow i propagacją wsteczną (gdzie podajesz sumę gradientów wszystkich obserwowalnych jako gradient dla określonego symbolu).

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)>

Tutaj widzisz, że pierwszy wpis to oczekiwanie względem Pauli X, a drugi to oczekiwanie względem Pauli Z. Teraz, kiedy weźmiesz gradient:

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]]

Tutaj potwierdziłeś, że suma gradientów dla każdego obserwowalnego jest rzeczywiście gradientem $ \ alpha $. To zachowanie jest obsługiwane przez wszystkie różniczki kwantowe TensorFlow i odgrywa kluczową rolę w kompatybilności z resztą TensorFlow.

4. Zaawansowane użycie

Tutaj dowiesz się, jak zdefiniować własne niestandardowe procedury różnicowania dla obwodów kwantowych. Wszystkie wyróżniacze, które istnieją w podklasie tfq.differentiators.Differentiator Quantum tfq.differentiators.Differentiator . Różnica musi implementować differentiate_analytic i differentiate_sampled .

Poniższe przykłady wykorzystują konstrukcje TensorFlow Quantum do implementacji rozwiązania w postaci zamkniętej z pierwszej części tego samouczka.

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

    def __init__(self):
        pass

    @tf.function
    def get_gradient_circuits(self, programs, symbol_names, symbol_values):
        """Return circuits to compute gradients for given forward pass circuits.

        When implementing a gradient, it is often useful to describe the
        intermediate computations in terms of transformed versions of the input
        circuits. The details are beyond the scope of this tutorial, but interested
        users should check out the differentiator implementations in the TFQ library
        for examples.
        """
        raise NotImplementedError(
            "Gradient circuits are not implemented in this tutorial.")

    @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

Ten nowy wyróżnik może być teraz używany z istniejącymi obiektami tfq.layer :

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

Ten nowy wyróżnik może być teraz używany do generowania zróżnicowanych operacji.

# 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.8]]
Gradient: [[1.8465817]]
---Original---
Forward:  0.80901700258255
Gradient: 1.8063604831695557