TensorFlow Quantum 0.2.0 Tutorials : 勾配を計算する (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 03/15/2020 (0.2.0)
* 本ページは、TensorFlow Quantum の以下のページを翻訳した上で適宜、補足説明したものです:
- Tutorials : Calculate gradients
* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。
Tutorials : 勾配を計算する
このチュートリアルは量子回路の期待値のための勾配計算アルゴリズムを探究します。ある可観測量の期待値の勾配を計算することは複雑なプロセスです。可観測量の期待値は書き下すことが常に容易な解析的勾配公式のような高級品は持ちません — 書き下すことが容易な解析的勾配公式を持つ行列乗算やベクトル加算のような伝統的な機械学習変換と違って。結果的に、異なるシナリオに対して役立つ異なる量子勾配計算法があります。このチュートリアルは 2 つの異なる微分スキームを比較して制約します。
セットアップ
TensorFlow Quantum をインストールします :
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.
可観測量とともに :
pauli_x = cirq.X(qubit) pauli_x
cirq.X.on(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 = sim.simulate(my_circuit, params).final_state return op.expectation_from_wavefunction(final_state, {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
微分器の必要性
より大きな回路では、与えられた量子回路の勾配を正確に計算する公式を常に持つほど幸運ではありません。単純な公式が勾配を計算するために十分ではないイベントでは、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.8090171]], 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.816]], 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 0x7f1e546b1278>
# 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()
<matplotlib.legend.Legend at 0x7f1e2c740a20>
ここで、有限差分式は解析的なケースでは勾配自身を計算するために高速ですが、サンプリングベースの方法ではそれは非常に noisy であったことを見ることができます。良い勾配が計算できることを確実にするためにより注意深いテクニックが使用されなければなりません。次に解析的期待値勾配計算のためには上手く適合しないが、しかし現実世界のサンプリングベースのケースで遥かに良く遂行する、遥かに遅いテクニックを見ます :
# 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()
WARNING:tensorflow:AutoGraph could not transform> and will run it as-is. Please report this to the TensorFlow team. When filing the bug, set the verbosity to 10 (on Linux, `export AUTOGRAPH_VERBOSITY=10`) and attach the full output. Cause: unexpected indent ( , line 77) WARNING: AutoGraph could not transform > and will run it as-is. Please report this to the TensorFlow team. When filing the bug, set the verbosity to 10 (on Linux, `export AUTOGRAPH_VERBOSITY=10`) and attach the full output. Cause: unexpected indent ( , line 77) <matplotlib.legend.Legend at 0x7f1e2c5a79b0>
上からある微分器が特定の研究シナリオのために最善に使用されることを見ることができます。一般に、デバイスノイズ等に堅牢な遅いサンプルベースの方法はより「現実世界」設定でアルゴリズムをテストまたは実装するとき素晴らしい微分器です。有限差分のような高速な方法は解析的計算のために素晴らしくそして貴方はより高い処理能力を望みますが、アルゴリズムのデバイス実行可能性にはまだ関心を持っていません。
3. マルチ可観測量
2 番目の可観測量を導入して TensorFlow Quantum が単一回路のためのマルチ可観測量をどのようにサポートするかを見ましょう。
pauli_z = cirq.Z(qubit) pauli_z
cirq.Z.on(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 で 1 つ以上の可観測量を定義することはより多くの項で $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([[0., 1.]], dtype=float32)>
ここで最初のエントリは Pauli X に関する期待値で、2 番目は Pauli Z に関する期待値であることを見ます。今次のように勾配を取るとき :
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.0917215]]
ここで各可観測量のための勾配の総計が実際に $\alpha$ の勾配であることを検証しました。この動作は総ての TensorFlow Quantum 微分器でサポートされて TensorFlow のその他の部分との互換性で重要な役割りを果たします。
4. 上級使用方法
ここでは量子回路のために貴方自身のカスタム微分器ルーチンをどのように定義するかを学習します。TensorFlow Quantum の内側に存在する総ての微分器は tfq.differentiators.Differentiator をサブクラス化しています。微分器は differentiate_analytic と differentiate_sampled を実装しなければなりません。
次はこのチュートリアルの最初のパートから closed 形式解を実装するために TensorFlow Quantum 構成物を使用します。
class MyDifferentiator(tfq.differentiators.Differentiator): """A Toy differentiator for.""" 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
この新しい微分器は今では既存の 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')
新しい微分器は今では微分可能な ops を生成するために使用できます。
Key Point: 以前に op に装着された微分器は新しい op に装着する前にリフレッシュされなければなりません、何故ならば微分器は一度に 1 つの op だけに装着が許されるからです。
# 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.79]] Gradient: [[1.8465817]] ---Original--- Forward: 0.80901700258255 Gradient: 1.8063604831695557
以上