Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
tensorflow
GitHub Repository: tensorflow/docs-l10n
Path: blob/master/site/ko/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.

그래디언트 계산하기

이 튜토리얼에서는 양자 회로의 기대 값에 대한 그래디언트 계산 알고리즘을 탐색합니다.

양자 회로에서 관찰 가능한 특정 기대 값의 그래디언트를 계산하는 것은 복잡한 프로세스입니다. 관찰 가능 항목의 기대 값은 기록하기 쉬운 분석적 그래디언트 수식이 있는 행렬 곱셈 또는 벡터 더하기와 같은 기존의 머신러닝 변환과 달리 언제든 기록하기 쉬운 분석적 그래디언트 수식을 사용할 수 없습니다. 결과적으로 다양한 시나리오에 유용한 다양한 양자 그래디언트 계산 방법이 있습니다. 이 튜토리얼에서는 두 가지의 다른 미분 체계를 비교하고 대조합니다.

설정

!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(π alpha)⟨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. 다중 observable

두 번째 observable을 소개하고 TensorFlow Quantum이 단일 회로에 대해 여러 observable을 지원하는 방법을 살펴보겠습니다.

pauli_z = cirq.Z(qubit) pauli_z

이 observable이 이전과 같은 회로에서 사용된다면 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에서 하나 이상의 observable을 정의하는 것은 gg에 더 많은 용어를 추가하는 것과 같습니다.

이것은 회로에서 특정 심볼의 그래디언트가 해당 회로에 적용된 해당 심볼의 각 observable에 대해 그래디언트의 합과 동일함을 의미합니다. 이는 TensorFlow 그래디언트 가져오기 및 역전파(특정 심볼에 대한 그래디언트로 모든 observable에 대한 그래디언트 합계를 제공)와 호환됩니다.

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이고, 두 번째 항목은 예상 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())

여기에서 각 observable의 그래디언트의 합이 실제로 α\alpha의 그래디언트임을 확인했습니다. 이 동작은 모든 TensorFlow Quantum 미분기에서 지원하며 나머지 TensorFlow와의 호환성에 중요한 역할을 합니다.

4. 고급 사용법

모든 차별화 요소는 TensorFlow Quantum 서브 클래스 tfq.differentiators.Differentiator 내부에 있습니다. 차별화 요소를 구현하려면 사용자가 두 인터페이스 중 하나를 구현해야 합니다. 표준적인 방법은 그래디언트 추정치를 얻기 위해 측정할 회로를 기본 클래스에 알려주는 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')

이제 이 새로운 미분기를 사용하여 미분 ops를 생성할 수 있습니다.

요점: 차별화 요소는 한 번에 하나의 op에만 연결할 수 있으므로 이전 op에 연결된 미분기는 새 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([[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이 제공하는 모든 미분기를 사용하고 자신만의 미분기를 정의할 수 있습니다.