Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
tensorflow
GitHub Repository: tensorflow/docs-l10n
Path: blob/master/site/ja/quantum/tutorials/gradients.ipynb
25118 views
Kernel: Python 3
#@title Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License.

勾配の計算

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

量子回路で特定のオブザーバブルの期待値の勾配を計算することは、複雑なプロセスです。行列の乗算やベクトルの加算などの従来の機械学習変換では簡単に使用できる解析的勾配式がありますが、オブザーバブルの期待値には、このような解析的勾配式は必ずしもありません。そのため、シナリオに適したさまざまな量子勾配計算方法を使用する必要があります。このチュートリアルでは、2 つの異なる微分スキームを比較対照します。

セットアップ

!pip install tensorflow==2.7.0

TensorFlow Quantum をインストールします。

!pip install tensorflow-quantum==0.7.2
# Update package resources to account for version changes. import importlib, pkg_resources importlib.reload(pkg_resources)

次に、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)

オブザーバブルは以下のとおりです。

pauli_x = cirq.X(qubit) pauli_x

この演算子を見ると、Y(α)XY(α)=sin(πα)⟨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))

f1(α)=Y(α)XY(α)f_{1}(\alpha) = ⟨Y(\alpha)| X | Y(\alpha)⟩ と定義すると f1(α)=πcos(πα)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))

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

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

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

これは、勾配における深刻な精度の問題につながる可能性があります。

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

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

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

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

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

2 番目のオブザーバブルを使用し、TensorFlow Quantum が 1 つの回路に対して複数のオブザーバブルをサポートする方法を見てみましょう。

pauli_z = cirq.Z(qubit) pauli_z

このオブザーバブルが以前と同じ回路で使用されている場合、f2(α)=Y(α)ZY(α)=cos(πα)f_{2}(\alpha) = ⟨Y(\alpha)| Z | Y(\alpha)⟩ = \cos(\pi \alpha) およびf2(α)=πsin(πα)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))

(ほぼ)一致します。

次に、g(α)=f1(α)+f2(α)g(\alpha) = f_{1}(\alpha) + f_{2}(\alpha) を定義すると、g(α)=f1(α)+f2(α)g'(\alpha) = f_{1}^{'}(\alpha) + f^{'}_{2}(\alpha)になります。TensorFlow Quantum で複数のオブザーバブルを定義して回路と共に使用するには、gg にさらに項を追加します。

これは、回路内の特定のシンボルの勾配が、その回路に適用されたそのシンボルの各オブザーバブルに関する勾配の合計に等しいことを意味します。これは、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]])

ここで、最初のエントリは期待値 w.r.t Pauli X であり、2 番目のエントリは期待値 w.r.t 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())

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

4. 高度な使用

TensorFlow Quantum 内にあるすべての微分器は tfq.differentiators.Differentiator をサブクラス化します。微分器を実装するには、ユーザーは 2 つのインターフェースのいずれかを実装する必要があります。標準的なのは get_gradient_circuits を実装することで、これは勾配の推定を取得するためにどの回路を測定するのかを基本クラスに指定します。または、differentiate_analyticdifferentiate_sampled をオーバーロードすることもできます。クラス tfq.differentiators.Adjoint はこの経路を使用します。

以下は TensorFlow Quantum を使用して、サーキットの勾配を実装しています。パラメーターシフトの小さな例を使用します。

上記で定義した α=Yα0|\alpha⟩ = Y^{\alpha}|0⟩ という回路を思い出してみましょう。前と同様に、XX 観測可能に対するこの回路の期待値として関数を定義できます(f(α)=αXαf(\alpha) = ⟨\alpha|X|\alpha⟩)。パラメーターシフトの規則を使用すると、この回路では、導関数は αf(α)=π2f(α+12)π2f(α12)\frac{\partial}{\partial \alpha} f(\alpha) = \frac{\pi}{2} f\left(\alpha + \frac{1}{2}\right) - \frac{ \pi}{2} f\left(\alpha - \frac{1}{2}\right) であることを見つけ出すことができます。get_gradient_circuits 関数は、この導関数の成分を返します。

class MyDifferentiator(tfq.differentiators.Differentiator): """A Toy differentiator for <Y^alpha | X |Y^alpha>.""" def __init__(self): pass def get_gradient_circuits(self, programs, symbol_names, symbol_values): """Return circuits to compute gradients for given forward pass circuits. Every gradient on a quantum computer can be computed via measurements of transformed quantum circuits. Here, you implement a custom gradient for a specific circuit. For a real differentiator, you will need to implement this function in a more general way. See the differentiator implementations in the TFQ library for examples. """ # The two terms in the derivative are the same circuit... batch_programs = tf.stack([programs, programs], axis=1) # ... with shifted parameter values. shift = tf.constant(1/2) forward = symbol_values + shift backward = symbol_values - shift batch_symbol_values = tf.stack([forward, backward], axis=1) # Weights are the coefficients of the terms in the derivative. num_program_copies = tf.shape(batch_programs)[0] batch_weights = tf.tile(tf.constant([[[np.pi/2, -np.pi/2]]]), [num_program_copies, 1, 1]) # The index map simply says which weights go with which circuits. batch_mapper = tf.tile( tf.constant([[[0, 1]]]), [num_program_copies, 1, 1]) return (batch_programs, symbol_names, batch_symbol_values, batch_weights, batch_mapper)

Differentiator の基本クラスは、上記で見たパラメーターシフトの式のように、get_gradient_circuits から返された成分を使用して導関数を計算します。この新しい微分器を 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')

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

重要点:微分器は一度に 1 つの演算にしか接続できないため、以前に演算に接続されていた微分器は、新しい演算に接続する前に更新する必要があります。

# 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([[5000]]) 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))

成功:TensorFlow Quantum が提供するすべての微分器を使用して、独自の微分器を定義できるようになりました。