今日のローカルTensorFlowEverywhereイベントの出欠確認!
このページは Cloud Translation API によって翻訳されました。
Switch to English

勾配を計算する

TensorFlow.orgで表示 GoogleColabで実行 GitHubでソースを表示ノートブックをダウンロード

このチュートリアルでは、量子回路の期待値の勾配計算アルゴリズムについて説明します。

量子回路で観測可能な特定の期待値の勾配を計算することは、複雑なプロセスです。オブザーバブルの期待値には、書き留めるのが簡単な分析勾配式を持つ行列乗算やベクトル加算などの従来の機械学習変換とは異なり、常に書き留めるのが簡単な分析勾配式を持つという贅沢はありません。その結果、さまざまなシナリオに役立つさまざまな量子勾配計算方法があります。このチュートリアルでは、2つの異なる微分スキームを比較対照します。

セットアップ

pip install -q tensorflow==2.3.1

TensorFlowQuantumをインストールします。

pip install -q tensorflow-quantum

次に、TensorFlowとモジュールの依存関係をインポートします。

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.予備

量子回路の勾配計算の概念をもう少し具体的にしましょう。次のようなパラメータ化された回路があるとします。

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

観察可能なものとともに:

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

この演算子を見ると、$⟨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

$ f_ {1}(\ alpha)=⟨Y(\ alpha)|を定義するとX | Y(\ alpha)⟩$次に$ f_ {1} ^ {'}(\ alpha)= \ pi \ cos(\ pi \ alpha)$。これを確認しましょう:

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.差別化要因の必要性

より大きな回路では、与えられた量子回路の勾配を正確に計算する式を持っていることは必ずしも幸運ではありません。単純な式では勾配を計算するのに十分でない場合、 tfq.differentiators.Differentiatorクラスを使用すると、回路の勾配を計算するためのアルゴリズムを定義できます。たとえば、TensorFlow Quantum(TFQ)で上記の例を次のように再現できます。

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

ただし、サンプリングに基づいて期待値を推定するように切り替えると(実際のデバイスで何が起こるか)、値が少し変わる可能性があります。これは、見積もりが不完全であることを意味します。

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

これは、勾配に関しては、すぐに深刻な精度の問題に発展する可能性があります。

# 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

ここでは、有限差分式は分析の場合に勾配自体を計算するのに高速ですが、サンプリングベースの方法に関してはノイズが多すぎることがわかります。適切な勾配を計算できるようにするには、より注意深い手法を使用する必要があります。次に、分析的な期待勾配の計算にはあまり適していないが、実際のサンプルベースのケースでははるかに優れたパフォーマンスを発揮する、はるかに遅い手法を見ていきます。

# 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

上記から、特定の差別化要因が特定の研究シナリオに最適であることがわかります。一般に、デバイスノイズなどに対して堅牢な、低速のサンプルベースの方法は、より「現実の」設定でアルゴリズムをテストまたは実装する場合の大きな差別化要因です。有限差分のようなより高速な方法は分析計算に最適であり、より高いスループットが必要ですが、アルゴリズムのデバイスの実行可能性にはまだ関心がありません。

3.複数のオブザーバブル

2番目のオブザーバブルを紹介し、TensorFlowQuantumが単一の回路に対して複数のオブザーバブルをサポートする方法を見てみましょう。

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

このオブザーバブルが以前と同じ回路で使用されている場合、$ f_ {2}(\ alpha)=⟨Y(\ alpha)|になります。 Z | Y(\ alpha)⟩= \ cos(\ pi \ alpha)$および$ f_ {2} ^ {'}(\ alpha)=-\ pi \ sin(\ pi \ alpha)$。クイックチェックを実行します。

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

それは一致です(十分に近い)。

ここで、$ g(\ alpha)= f_ {1}(\ alpha)+ f_ {2}(\ alpha)$を定義すると、$ g '(\ alpha)= f_ {1} ^ {'}(\ alpha) + f ^ {'} _ {2}(\ alpha)$。 TensorFlow Quantumで複数のオブザーバブルを定義して回路と一緒に使用することは、$ g $にさらに用語を追加することと同じです。

これは、回路内の特定のシンボルの勾配が、その回路に適用されたそのシンボルで観測可能な各シンボルに関する勾配の合計に等しいことを意味します。これは、TensorFlowの勾配取得およびバックプロパゲーション(特定のシンボルの勾配としてすべてのオブザーバブルの勾配の合計を指定する)と互換性があります。

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

ここで、最初のエントリはPauli Xの期待値であり、2番目のエントリはPauliZの期待値です。勾配をとると次のようになります。

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

ここで、各オブザーバブルの勾配の合計が実際に$ \ alpha $の勾配であることを確認しました。この動作は、すべてのTensorFlow Quantum差別化要因によってサポートされており、TensorFlowの他の部分との互換性において重要な役割を果たします。

4.高度な使用法

ここでは、量子回路用に独自のカスタム微分ルーチンを定義する方法を学習します。 TensorFlowQuantumサブクラスtfq.differentiators.Differentiator内に存在するすべての微分tfq.differentiators.Differentiator 。差別化differentiate_analyticdifferentiate_sampleddifferentiate_sampled実装する必要がありdifferentiate_sampled

以下では、TensorFlow Quantumコンストラクトを使用して、このチュートリアルの最初の部分の閉じた形式のソリューションを実装します。

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

この新しい差別化要因は、既存の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

この新しい微分器を使用して、微分可能な演算を生成できるようになりました。

# 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