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

Calcule gradientes

Este tutorial explora algoritmos de cálculo de gradiente para os valores esperados de circuitos quânticos.

Calcular o gradiente do valor esperado de determinado observável em um circuito quântico é um processo complexo. Os valores esperados dos observáveis não possuem o luxo de ter fórmulas de gradiente analíticas que são sempre fáceis de escrever — ao contrário das transformações de aprendizado de máquina tradicionais, como a multiplicação de matriz ou a adição de vetores, que possuem fórmulas de gradiente analíticas que são fáceis de escrever. Como resultado, há diferentes métodos para o cálculo de gradientes quânticos que podem ser úteis para diferentes cenários. Este tutorial compara e contrasta dos esquemas de diferenciação diferentes.

Configuração

!pip install tensorflow==2.7.0

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

Agora importe o TensorFlow e as dependências de módulo:

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. Preliminar

Vamos tornar a noção de cálculo de gradientes para circuitos quânticos um pouco mais concreta. Suponha que você tem um circuito parametrizado como este:

qubit = cirq.GridQubit(0, 0) my_circuit = cirq.Circuit(cirq.Y(qubit)**sympy.Symbol('alpha')) SVGCircuit(my_circuit)

Junto com um observável:

pauli_x = cirq.X(qubit) pauli_x

Olhando esse operador, você sabe que 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))

e, se você definir f1(α)=Y(α)XY(α)f_{1}(\alpha) = ⟨Y(\alpha)| X | Y(\alpha)⟩, então f1(α)=πcos(πα)f_{1}^{'}(\alpha) = \pi \cos(\pi \alpha). Vamos verificar isso:

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. Necessidade de um diferenciador

Com circuitos maiores, você nem sempre terá a sorte de ter uma fórmula que calcula precisamente os gradientes de um determinado circuito quântico. Caso uma fórmula simples não seja suficiente para calcular o gradiente, a classe tfq.differentiators.Differentiator permite que você defina algoritmos para computar os gradientes dos seus circuitos. Por exemplo, você pode recriar o exemplo acima no TensorFlow Quantum (TFQ) com:

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

No entanto, se você quiser estimar a expectativa com base na amostragem (o que aconteceria em um dispositivo real), os valores podem mudar um pouco. Isso significa que agora você tem uma estimativa imperfeita:

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

Isso pode rapidamente se tornar um problema de exatidão mais sério quando se trata de gradientes:

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

Aqui você pode ver que, embora a fórmula de diferença finita seja rápida em computar os gradientes no caso analítico, quando se trata de métodos baseados em amostragem, há muito ruído. É preciso usar técnicas mais cuidadosas para garantir que um bom gradiente possa ser calculado. Em seguida, você analisará uma técnica muito mais lenta que não seria tão adequada para cálculos de gradiente esperado analíticos, mas tem um desempenho muito melhor no caso real baseado em amostra.

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

Acima, você pode ver que determinados diferenciadores são mais adequados para cenários de pesquisa específicos. Em geral, os métodos mais lentos baseados em amostragem que são robustos ao ruído dos dispositivos etc. são ótimos diferenciadores ao testar ou implementar algoritmos em um cenário mais "mundo real". Métodos mais rápidos, como a diferença finita, são ótimos para cálculos analíticos e para quando você quer maior produtividade, mas não está preocupado com a viabilidade de dispositivos do seu algoritmo.

3. Vários observáveis

Vamos apresentar um segundo observável e ver como o TensorFlow Quantum é compatível com vários observáveis em um único circuito.

pauli_z = cirq.Z(qubit) pauli_z

Se esse observável for usado com o mesmo circuito que antes, você terá f2(α)=Y(α)ZY(α)=cos(πα)f_{2}(\alpha) = ⟨Y(\alpha)| Z | Y(\alpha)⟩ = \cos(\pi \alpha) e f2(α)=πsin(πα)f_{2}^{'}(\alpha) = -\pi \sin(\pi \alpha). Faça uma rápida verificação:

test_value = 0. print('Finite difference:', my_grad(pauli_z, test_value)) print('Sin formula: ', -np.pi * np.sin(np.pi * test_value))

Temos uma correspondência (próxima o suficiente).

Agora, se você definir g(α)=f1(α)+f2(α)g(\alpha) = f_{1}(\alpha) + f_{2}(\alpha), então g(α)=f1(α)+f2(α)g'(\alpha) = f_{1}^{'}(\alpha) + f^{'}_{2}(\alpha). A definição de mais de um observável no TensorFlow Quantum para ser usado com um circuito é equivalente a adicionar mais termos a gg.

Isso significa que o gradiente de um símbolo específico em um circuito é igual à soma dos gradientes em relação a cada observável para esse símbolo aplicado ao circuito. Isso é compatível com a obtenção de gradiente e a retropropagação do TensorFlow (onde você dá a soma dos gradientes de todos os observáveis como o gradiente de um determinado símbolo).

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

Aqui, você vê que a primeira entrada é o X de Pauli esperado do w.r.t, e a segunda é o Z de Pauli esperado do w.r.t. Agora, você obtém o gradiente:

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

Aqui, você verificou que a soma dos gradientes para cada observável é realmente o gradiente de α\alpha. Esse comportamento é compatível com todos os diferenciadores do TensorFlow Quantum e desempenha um papel crucial na compatibilidade com o resto do TensorFlow.

4. Uso avançado

Todos os diferenciadores que existem dentro da subclasse tfq.differentiators.Differentiator do TensorFlow. Para implementar um diferenciador, um usuário precisa implementar uma das duas interfaces. O padrão é implementar get_gradient_circuits, que diz à classe de base quais circuitos medir para obter uma estimativa do gradiente. Como alternativa, você pode sobrecarregar differentiate_analytic e differentiate_sampled. A classe tfq.differentiators.Adjoint segue esse caminho.

O código a seguir usa o TensorFlow Quantum para implementar o gradiente de um circuito. Você usará um pequeno exemplo da mudança de parâmetros.

Chame novamente o circuito que você definiu acima, α=Yα0|\alpha⟩ = Y^{\alpha}|0⟩. Como antes, você pode definir uma função como o valor esperado desse circuito para o observável XX, f(α)=αXαf(\alpha) = ⟨\alpha|X|\alpha⟩. Usando as regras de mudança de parâmetro, para esse circuito, você pode encontrar que a derivada é α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). A função get_gradient_circuits retorna os componentes dessa derivada.

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)

A classe de base Differentiator usa os componentes retornados de get_gradient_circuits para calcular a derivada, como na fórmula de mudança de parâmetro que você viu acima. Esse novo diferenciador agora pode ser usado com objetos tfq.layer existentes:

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

Esse novo diferenciador agora pode ser usado para gerar ops diferenciáveis.

Ponto importante: um diferenciador que foi ligado anteriormente a uma op precisa ser atualizado antes de ser ligado a uma nova op, porque ele só pode ser ligado a uma de cada vez.

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

Sucesso: agora você pode usar todos os diferenciadores que o TensorFlow Quantum tem a oferecer — e definir os seus.