在本节中,我们将仔细研究如何使用基于梯度的方法训练基于电路的模型。我们将看看这些模型的限制,以及我们如何克服它们。
与经典模型一样,我们可以训练参数化量子电路模型来执行数据驱动的任务。从数据中学习任意函数的任务在数学上表示为代价或者损失函数(也称为目标函数) f ( θ ⃗ ) f(\vec \theta) f(θ)的最小化,相对于参数向量 θ ⃗ \vec \theta θ。通常,在训练参数化量子电路模型时,我们想要取最小值的函数是期望值 < Ψ ( θ ⃗ ) ∣ H ^ ∣ Ψ ( θ ⃗ ) > \left<\Psi(\vec \theta)\right|\hat H\left|\Psi(\vec \theta)\right> ⟨Ψ(θ) H^ Ψ(θ)⟩
我们可以使用许多不同类型的算法来优化变分电路的参数, U θ U_\theta Uθ(基于梯度的、进化的和无梯度的方法)。在本课程中,我们将讨论基于梯度的方法。
假设我们有一个函数 f ( θ ⃗ ) f(\vec \theta) f(θ),我们可以得到函数的梯度 ∇ f ( θ ⃗ ) \nabla f(\vec \theta) ∇f(θ),从某个初始点开始。最小化函数的最简单方法是朝着函数最陡下降的方向更新参数: θ ⃗ n + 1 = θ ⃗ n − η ∇ f ( θ ⃗ ) \vec \theta_{n+1}=\vec \theta_n-\eta\nabla f(\vec \theta) θn+1=θn−η∇f(θ)
,其中 η \eta η为学习率,即一个小的、正的超参数控制更新的大小。我们继续这样做,直到收敛到局部最小值, f ( θ ⃗ ∗ ) f(\vec \theta^*) f(θ∗)。
这种技术被称为梯度下降法或香草味(vanilla)梯度下降法,因为它是普通的梯度下降,我们没有对它做任何特殊的处理。
from qiskit.circuit.library import RealAmplitudes
ansatz = RealAmplitudes(num_qubits=2, reps=1,
entanglement='linear').decompose()
ansatz.draw()
下一步我们需要定义一个哈密顿量,比如说 H ^ = Z ^ ⊗ Z ^ \hat H=\hat Z \otimes \hat Z H^=Z^⊗Z^
from qiskit.opflow import Z, I
hamiltonian = Z ^ Z
把它们放在一起得到期望值: < Ψ ( θ ⃗ ) ∣ H ^ ∣ Ψ ( θ ⃗ ) > \left<\Psi(\vec \theta)\right|\hat H\left|\Psi(\vec \theta)\right> ⟨Ψ(θ) H^ Ψ(θ)⟩
from qiskit.opflow import StateFn, PauliExpectation
expectation = StateFn(hamiltonian, is_measurement=True) @ StateFn(ansatz)
pauli_basis = PauliExpectation().convert(expectation)
接下来,我们编写一个函数来模拟期望值的测量
from qiskit import Aer
from qiskit.utils import QuantumInstance
from qiskit.opflow import PauliExpectation, CircuitSampler
quantum_instance = QuantumInstance(Aer.get_backend('qasm_simulator'),
# we'll set a seed for reproducibility
shots = 8192, seed_simulator = 2718,
seed_transpiler = 2718)
sampler = CircuitSampler(quantum_instance)
def evaluate_expectation(theta):
value_dict = dict(zip(ansatz.parameters, theta))
result = sampler.convert(pauli_basis, params=value_dict).eval()
return np.real(result)
具体说来,我们先固定点 p ⃗ \vec p p和指标 i i i,之后思考:期望值在 p ⃗ \vec p p点关于某一参数 θ i \theta_i θi的导数是什么?
∂ ∂ θ i < Ψ ( θ ⃗ ) ∣ H ^ ∣ θ ⃗ > ∣ θ ⃗ = p ⃗ \dfrac{\partial}{\partial \theta_i}\left<\Psi(\vec \theta)\right|\hat H\left|\vec \theta\right>\bigg|_{\vec \theta = \vec p} ∂θi∂⟨Ψ(θ) H^ θ⟩ θ=p
我们选择一个随机的点作为 p ⃗ \vec p p并令指标为 i = 2 i=2 i=2(注意我们从0开始计数)
import numpy as np
point = np.random.random(ansatz.num_parameters)
INDEX = 2
有限差分梯度(Finite difference gradients)
可以说,近似梯度最简单的方法是用有限差分方式。这独立于函数内部可能非常复杂的结构。
如果我们对估计 f ( θ ⃗ ) f(\vec \theta) f(θ)的梯度很感兴趣,我们可以选择一些小的具体 ε \varepsilon ε并计算 f ( θ + ϵ ) f(\theta + \epsilon) f(θ+ϵ)和 f ( θ − ϵ ) f(\theta - \epsilon) f(θ−ϵ)。于是,我们可以记差分为
∇ f ( θ ⃗ ) ≈ 1 2 ε ( f ( θ ⃗ + ε ) − f ( θ ⃗ − ε ) ) \nabla f(\vec \theta)\approx\dfrac{1}{2\varepsilon}\bigg(f(\vec \theta + \varepsilon)-f(\vec \theta - \varepsilon)\bigg) ∇f(θ)≈2ε1(f(θ+ε)−f(θ−ε))
EPS = 0.2
# make identity vector with a 1 at index ``INDEX``, otherwise 0
e_i = np.identity(point.size)[:, INDEX]
plus = point + EPS * e_i
minus = point - EPS * e_i
finite_difference = (
evaluate_expectation(plus) - evaluate_expectation(minus)) / (2 * EPS)
print(finite_difference)
如果我们不想手写上述流程,我们可以直接使用Qiskit的Gradient类
from qiskit.opflow import Gradient
shifter = Gradient('fin_diff', analytic=False, epsilon=EPS)
grad = shifter.convert(expectation, params=ansatz.parameters[INDEX])
print(grad)
value_dict = dict(zip(ansatz.parameters, point))
sampler.convert(grad, value_dict).eval().real
有限差分梯度在噪声函数上是不稳定的,使用精确的梯度公式可以更稳定。这可以从上面看到,虽然这两个计算使用相同的公式,但由于射击噪声,它们产生不同的结果。在下面的示例图像中,我们可以看到“有噪声的有限差分梯度”实际上指向与真实梯度相反的方向!
分析梯度对梯度的解析公式进行评估。一般来说,这是相当困难的,因为我们必须进行手动计算,但对于基于电路的梯度,参考文献1介绍了一个很好的理论结果,给出了计算梯度的简单公式:参数移位规则。
对于仅由泡利旋转组成的简单电路,没有任何系数,则该规则表明解析梯度为:
∂ f ∂ θ i = f ( θ ⃗ + π 2 e ⃗ i ) − f ( θ ⃗ − π 2 e ⃗ i ) 2 \frac{\partial f}{\partial \theta_i}=\dfrac{f(\vec \theta + \frac{\pi}{2}\vec e_i)-f(\vec \theta - \frac{\pi}{2}\vec e_i)}{2} ∂θi∂f=2f(θ+2πei)−f(θ−2πei)
这一表达式和有限差分梯度十分相像。
让我们尝试亲手计算:
EPS = np.pi / 2
e_i = np.identity(point.size)[:, INDEX]
plus = point + EPS * e_i
minus = point - EPS * e_i
finite_difference = (
evaluate_expectation(plus) - evaluate_expectation(minus)) / 2
print(finite_difference)
当然也可以使用Qiskit的Gradient类
shifter = Gradient() # parameter-shift rule is the default
grad = shifter.convert(expectation, params=ansatz.parameters[INDEX])
sampler.convert(grad, value_dict).eval().real
我们看到,计算的分析梯度与计算的有限差分梯度相当相似。
现在我们知道如何计算梯度,让我们尝试优化期望值!
首先,我们为再现性确定一个初始点。
# initial_point = np.random.random(ansatz.num_parameters)
initial_point = np.array([0.43253681, 0.09507794, 0.42805949, 0.34210341])
就像我们用函数来求期望值一样,我们需要一个函数来求梯度。
gradient = Gradient().convert(expectation)
gradient_in_pauli_basis = PauliExpectation().convert(gradient)
sampler = CircuitSampler(quantum_instance)
def evaluate_gradient(theta):
value_dict = dict(zip(ansatz.parameters, theta))
result = sampler.convert(gradient_in_pauli_basis,
params=value_dict).eval()
return np.real(result)
比较不同优化器的收敛程度,我们可以使用回调函数跟踪每一步的损失。
class OptimizerLog:
"""Log to store optimizer's intermediate results"""
def __init__(self):
self.loss = []
def update(self, _nfevs, _theta, ftheta, *_):
"""Save intermediate results. Optimizers pass many values
but we only store the third ."""
self.loss.append(ftheta)
from qiskit.algorithms.optimizers import GradientDescent
gd_log = OptimizerLog()
gd = GradientDescent(maxiter=300,
learning_rate=0.01,
callback=gd_log.update)
现在我们开始优化并绘制损失图!
result = gd.minimize(
fun=evaluate_expectation, # function to minimize
x0=initial_point, # initial point
jac=evaluate_gradient # function to evaluate gradient
)
import matplotlib.pyplot as plt
plt.figure(figsize=(7, 3))
plt.plot(gd_log.loss, label='vanilla gradient descent')
plt.axhline(-1, ls='--', c='C3', label='target')
plt.ylabel('loss')
plt.xlabel('iterations')
plt.legend();
我们在上面的例子中看到,我们能够使用梯度下降找到函数的最小值。然而,梯度下降并不总是最好的策略。
例如,如果我们看左边的图表,给定损失景观边缘的初始点 θ 0 = ( x 0 , y 0 ) \theta_0=(x_0,y_0) θ0=(x0,y0)和学习率 η \eta η,我们可以接近中心的最小值。
然而,看看右边的图表,损失景观在 x x x-维度中被压缩了,我们看到使用相同的初始点和学习率,我们找不到最小值。这是因为我们错误地假设损失情况相对于每个参数以相同的速率变化。这是因为我们错误地假设损失情况相对于每个参数以相同的速率变化。两种模型显示出相同的欧几里得距离来计算 ( x 0 , y 0 ) , ( x 1 , y 1 ) (x_0,y_0),(x_1,y_1) (x0,y0),(x1,y1)之间的,但这对模型B来说是不够的,因为这个度量不能捕获到相对的灵敏度。
自然梯度的概念是改变我们利用 θ n \theta_n θn判断 θ n + 1 \theta_{n+1} θn+1的方式,考虑模型的灵敏度。在香草梯度中,我们使用它们之间的欧几里得距离: d = ∣ ∣ θ ⃗ n + 1 − θ ⃗ n ∣ ∣ 2 d=||\vec \theta_{n+1}-\vec \theta_n||_2 d=∣∣θn+1−θn∣∣2,但我们发现这并没有考虑到损失情况。对于自然梯度,我们使用的距离取决于我们的模型:KaTeX parse error: Undefined control sequence: \ve at position 16: d=||\left<\Psi(\̲v̲e̲ ̲c\theta_n)|\Psi…
这个度量被称为量子费舍信息, g i j ( θ ⃗ ) g_{ij}(\vec \theta) gij(θ),并允许我们将欧几里得参数空间中的最陡下降变换为模型空间中的最陡下降。这被称为量子自然梯度,并在参考文献2中介绍,其中 θ ⃗ n + 1 = θ ⃗ n − η g − 1 ( θ ⃗ n ) ∇ f ( θ n ) \vec \theta_{n+1}=\vec \theta_{n}-\eta g^{-1}(\vec \theta_n)\nabla f(\theta_n) θn+1=θn−ηg−1(θn)∇f(θn)
我们可以在Qiskit中使用Natural Gradient而不是gradient来评估自然梯度:
from qiskit.opflow import NaturalGradient
与计算梯度的函数类似,我们现在可以编写一个计算自然梯度的函数。
natural_gradient = (NaturalGradient(regularization='ridge')
.convert(expectation))
natural_gradient_in_pauli_basis = PauliExpectation().convert(
natural_gradient)
sampler = CircuitSampler(quantum_instance, caching="all")
def evaluate_natural_gradient(theta):
value_dict = dict(zip(ansatz.parameters, theta))
result = sampler.convert(natural_gradient, params=value_dict).eval()
return np.real(result)
print('Vanilla gradient:', evaluate_gradient(initial_point))
print('Natural gradient:', evaluate_natural_gradient(initial_point))
正如你所看到的,它们确实不同!
让我们看看这是如何影响收敛的。
qng_log = OptimizerLog()
qng = GradientDescent(maxiter=300,
learning_rate=0.01,
callback=qng_log.update)
result = qng.minimize(evaluate_expectation,
initial_point,
evaluate_natural_gradient)
# Plot loss
plt.figure(figsize=(7, 3))
plt.plot(gd_log.loss, 'C0', label='vanilla gradient descent')
plt.plot(qng_log.loss, 'C1', label='quantum natural gradient')
plt.axhline(-1, c='C3', ls='--', label='target')
plt.ylabel('loss')
plt.xlabel('iterations')
plt.legend();
这看起来很棒!我们可以看到,量子自然梯度比香草梯度下降更快地接近目标。然而,这是以需要评估更多量子电路为代价的。
将我们的函数 f ( θ ⃗ ) f(\vec \theta) f(θ)视作一个向量,如果我们想求梯度 ∇ f ( θ ⃗ ) \nabla f(\vec \theta) ∇f(θ),我们需要对于每个参数计算 f ( θ ⃗ ) f(\vec \theta) f(θ)的偏导数,这意味着我们需要对 2 N 2N 2N个参数进行函数求值来计算梯度。
同时扰动随机近似(SPSA)是一种优化技术,我们从梯度中随机抽样,以减少评估的次数。因为我们不关心确切的值,而只关心收敛性,所以无偏抽样的平均效果应该是一样的。
在实践中,当精确梯度沿着平滑路径到达最小值时,由于随机抽样,SPSA会跳跃,但在给定与梯度相同的边界条件下,它会收敛。
它的性能如何?我们在Qiskit中使用SPSA算法。
from qiskit.algorithms.optimizers import SPSA
spsa_log = OptimizerLog()
spsa = SPSA(maxiter=300, learning_rate=0.01,
perturbation=0.01, callback=spsa_log.update)
result = spsa.minimize(evaluate_expectation, initial_point)
# Plot loss
plt.figure(figsize=(7, 3))
plt.plot(gd_log.loss, 'C0', label='vanilla gradient descent')
plt.plot(qng_log.loss, 'C1', label='quantum natural gradient')
plt.plot(spsa_log.loss, 'C0', ls='--', label='SPSA')
plt.axhline(-1, c='C3', ls='--', label='target')
plt.ylabel('loss')
plt.xlabel('iterations')
plt.legend();
我们可以看到SPSA基本上遵循梯度下降曲线,而且成本很低!
我们也可以对自然梯度做同样的事情,如参考文献3所述。我们将跳过这里的细节,但我们的想法是不仅从梯度中采样,而且将其扩展到量子费雪信息,从而扩展到自然梯度。
Qiskit将其实现为QNSPSA算法。让我们比较一下它的性能:
from qiskit.algorithms.optimizers import QNSPSA
qnspsa_log = OptimizerLog()
fidelity = QNSPSA.get_fidelity(ansatz,
quantum_instance,
expectation=PauliExpectation())
qnspsa = QNSPSA(fidelity, maxiter=300, learning_rate=0.01,
perturbation=0.01,
callback=qnspsa_log.update)
result = qnspsa.minimize(evaluate_expectation, initial_point)
# Plot loss
plt.figure(figsize=(7, 3))
plt.plot(gd_log.loss, 'C0', label='vanilla gradient descent')
plt.plot(qng_log.loss, 'C1', label='quantum natural gradient')
plt.plot(spsa_log.loss, 'C0', ls='--', label='SPSA')
plt.plot(qnspsa_log.loss, 'C1', ls='--', label='QN-SPSA')
plt.axhline(-1, c='C3', ls='--', label='target')
plt.ylabel('loss')
plt.xlabel('iterations')
plt.legend()
我们可以看到QNSPSA在某种程度上遵循自然梯度下降曲线。
香草和自然梯度成本是线性的和二次的,
而SPSA和QNSPSA的成本是常数,即与参数的数量无关。SPSA和QNSPSA的成本之间有一个小的抵消,因为需要更多的评估来近似自然梯度。
在这个近期量子计算的时代,电路评估是昂贵的,而且由于设备的噪声性质,读数并不完美。因此,在实践中,人们经常求助于SPSA。为了提高收敛性,我们不使用恒定的学习率,而是使用指数递减的学习率。下图显示了恒定学习率(虚线)与指数递减学习率(实线)之间的典型收敛。我们看到,恒定学习率的收敛是一条平滑的递减线,而指数递减率的收敛则更陡峭,更交错。如果你知道损失函数是什么样的,这就很有效了。
如果您没有指定学习率,Qiskit将尝试自动校准模型的学习率。
autospsa_log = OptimizerLog()
autospsa = SPSA(maxiter=300,
learning_rate=None,
perturbation=None,
callback=autospsa_log.update)
result = autospsa.minimize(evaluate_expectation, initial_point)
# Plot loss
plt.figure(figsize=(7, 3))
plt.plot(gd_log.loss, 'C0', label='vanilla gradient descent')
plt.plot(qng_log.loss, 'C1', label='quantum natural gradient')
plt.plot(spsa_log.loss, 'C0', ls='--', label='SPSA')
plt.plot(qnspsa_log.loss, 'C1', ls='--', label='QN-SPSA')
plt.plot(autospsa_log.loss, 'C3', label='Power-law SPSA')
plt.axhline(-1, c='C3', ls='--', label='target')
plt.ylabel('loss')
plt.xlabel('iterations')
plt.legend()
我们在这里看到,对于这个小模型,它是所有方法中效果最好的。对于较大的模型,收敛可能更像自然梯度。
我们已经看到,梯度训练在小样本模型上效果很好。但是,如果我们增加量子比特的数量,我们能期待同样的结果吗?为了研究这一点,我们测量不同模型尺寸的梯度的方差。这个想法很简单:如果方差真的很小,我们就没有足够的信息来更新参数。
让我们选择一个标准的参数化量子电路(RealAmplitudes),看看如果我们在计算梯度时增加量子比特和层的数量(即增加电路的宽度和深度)会发生什么。
from qiskit.opflow import I
def sample_gradients(num_qubits, reps, local=False):
"""Sample the gradient of our model for ``num_qubits`` qubits and
``reps`` repetitions.
We sample 100 times for random parameters and compute the gradient
of the first RY rotation gate.
"""
index = num_qubits - 1
# local or global operator
if local:
operator = Z ^ Z ^ (I ^ (num_qubits - 2))
else:
operator = Z ^ num_qubits
# real amplitudes ansatz
ansatz = RealAmplitudes(num_qubits, entanglement='linear', reps=reps)
# construct Gradient we want to evaluate for different values
expectation = StateFn(operator,
is_measurement=True).compose(StateFn(ansatz))
grad = Gradient().convert(expectation,
params=ansatz.parameters[index])
# evaluate for 100 different, random parameter values
num_points = 100
grads = []
for _ in range(num_points):
# points are uniformly chosen from [0, pi]
point = np.random.uniform(0, np.pi, ansatz.num_parameters)
value_dict = dict(zip(ansatz.parameters, point))
grads.append(sampler.convert(grad, value_dict).eval())
return grads
让我们从2到12个量子比特进行绘制。
num_qubits = list(range(2, 13))
reps = num_qubits # number of layers = numbers of qubits
gradients = [sample_gradients(n, r) for n, r in zip(num_qubits, reps)]
fit = np.polyfit(num_qubits, np.log(np.var(gradients, axis=1)), deg=1)
x = np.linspace(num_qubits[0], num_qubits[-1], 200)
plt.figure(figsize=(7, 3))
plt.semilogy(num_qubits,
np.var(gradients, axis=1),
'o-',
label='measured variance')
plt.semilogy(x,
np.exp(fit[0] * x + fit[1]),
'--', c='C3',
label=f'exponential fit w/ {fit[0]:.2f}')
plt.xlabel('number of qubits')
plt.ylabel(r'$\mathrm{Var}[\partial_{\theta 1}\langle E(\theta)\rangle]$')
plt.legend(loc='best');
噢,不!方差呈指数递减!这意味着我们的梯度包含越来越少的信息,我们将很难训练模型。这就是所谓的“贫瘠高原问题”,或“指数消失梯度”,在参考文献4和5中详细讨论。
对于这些贫瘠的高原,我们能做些什么吗?这是目前研究的热点问题,并提出了一些缓解荒无人烟高原的建议。
让我们来看看全球和当地的成本函数以及ansatz的深度如何影响贫瘠的高原。首先,我们将研究具有全局运算符的短深度单层电路。
num_qubits = list(range(2, 13))
fixed_depth_global_gradients = [sample_gradients(n, 1) for n in num_qubits]
fit = np.polyfit(num_qubits, np.log(np.var(fixed_depth_global_gradients,
axis=1)), deg=1)
x = np.linspace(num_qubits[0], num_qubits[-1], 200)
plt.figure(figsize=(7, 3))
plt.semilogy(num_qubits,
np.var(gradients, axis=1),
'o-',
label='global cost, linear depth')
plt.semilogy(num_qubits, np.var(fixed_depth_global_gradients, axis=1),
'o-',
label='global cost, constant depth')
plt.semilogy(x,
np.exp(fit[0] * x + fit[1]),
'--', c='C3',
label=f'exponential fit w/ {fit[0]:.2f}')
plt.xlabel('number of qubits')
plt.ylabel(r'$\mathrm{Var}[\partial_{\theta 1}\langle E(\theta)\rangle]$')
plt.legend(loc='best');
我们看到,具有全局操作符的短深度单层电路仍然给我们带来了贫瘠的高原。
如果我们使用局域操作符呢?
num_qubits = list(range(2, 13))
linear_depth_local_gradients = [sample_gradients(n, n,
local=True) for n in num_qubits]
fit = np.polyfit(num_qubits,
np.log(np.var(linear_depth_local_gradients,axis=1)),
deg=1)
x = np.linspace(num_qubits[0], num_qubits[-1], 200)
plt.figure(figsize=(7, 3))
plt.semilogy(num_qubits, np.var(gradients, axis=1),
'o-', label='global cost, linear depth')
plt.semilogy(num_qubits, np.var(fixed_depth_global_gradients, axis=1),
'o-', label='global cost, constant depth')
plt.semilogy(num_qubits, np.var(linear_depth_local_gradients, axis=1),
'o-', label='local cost, linear depth')
plt.semilogy(x, np.exp(fit[0] * x + fit[1]), '--', c='C3',
label=f'exponential fit w/ {fit[0]:.2f}')
plt.xlabel('number of qubits')
plt.ylabel(r'$\mathrm{Var}[\partial_{\theta 1}\langle E(\theta)\rangle]$')
plt.legend(loc='best');
我们看到,局域运算符的线路仍然给我们带来了贫瘠的高原。
有局域运算符的短深度、单层电路怎么样?
num_qubits = list(range(2, 13))
fixed_depth_local_gradients = [sample_gradients(n, 1,
local=True) for n in num_qubits]
fit = np.polyfit(num_qubits,
np.log(np.var(fixed_depth_local_gradients, axis=1)),
deg=1)
x = np.linspace(num_qubits[0], num_qubits[-1], 200)
plt.figure(figsize=(7, 3))
plt.semilogy(num_qubits, np.var(gradients, axis=1),
'o-', label='global cost, linear depth')
plt.semilogy(num_qubits, np.var(fixed_depth_global_gradients, axis=1),
'o-', label='global cost, constant depth')
plt.semilogy(num_qubits, np.var(linear_depth_local_gradients, axis=1),
'o-', label='local cost, linear depth')
plt.semilogy(num_qubits, np.var(fixed_depth_local_gradients, axis=1),
'o-', label='local cost, constant depth')
plt.semilogy(x, np.exp(fit[0] * x + fit[1]), '--', c='C3',
label=f'exponential fit w/ {fit[0]:.2f}')
plt.xlabel('number of qubits')
plt.ylabel(r'$\mathrm{Var}[\partial_{\theta 1}\langle E(\theta)\rangle]$')
plt.legend(loc='best');
我们看到局部运算符的方差,等深度电路梯度不会消失,也就是说,我们不会得到荒芜的高原。然而,这些电路通常很容易模拟,因此这些模型不会提供任何优于经典模型的优势。
这是分层训练的灵感,我们从一个可能不提供任何量子优势的基本电路开始,使用局部算子进行一层旋转。我们优化和固定这些参数,然后在下一步,我们添加第二层旋转使用局部算子,并优化和固定这些,然后继续进行我们想要的多少层。由于每个优化步骤仅使用具有局部操作符的定深电路,因此这可能避免了无效果的高原。
我们可以通过以下方式在Qiskit中实现这一点:
NUM_QUBITS = 6
OPERATOR = Z ^ Z ^ (I ^ (NUM_QUBITS - 4))
def minimize(circuit, optimizer):
"""
Args:
circuit (QuantumCircuit): (Partially bound) ansatz circuit to train
optimizer (Optimizer): Algorithm to use to minimize exp. value
Returns:
OptimizerResult: Result of minimization
"""
initial_point = np.random.random(circuit.num_parameters)
exp = StateFn(OPERATOR, is_measurement=True) @ StateFn(circuit)
grad = Gradient().convert(exp)
exp = PauliExpectation().convert(exp)
grad = PauliExpectation().convert(grad)
sampler = CircuitSampler(quantum_instance, caching="all")
def loss(theta):
values_dict = dict(zip(circuit.parameters, theta))
return np.real(sampler.convert(exp, values_dict).eval())
def gradient(theta):
values_dict = dict(zip(circuit.parameters, theta))
return np.real(sampler.convert(grad, values_dict).eval())
return optimizer.minimize(loss, initial_point, gradient)
def layerwise_training(ansatz, max_num_layers, optimizer):
"""
Args:
ansatz (QuantumCircuit): Single circuit layer to train & repeat
max_num_layers (int): Maximum number of layers
optimizer (Optimizer): Algorithm to use to minimize exp. value
Returns:
float: Lowest value acheived
list[float]: Best parameters found
"""
optimal_parameters = []
for reps in range(max_num_layers):
ansatz.reps = reps
# fix the already optimized parameters
values_dict = dict(zip(ansatz.parameters, optimal_parameters))
partially_bound = ansatz.bind_parameters(values_dict)
result = minimize(partially_bound, optimizer)
optimal_parameters += list(result.x)
print('Layer:', reps, ' Best Value:', result.fun)
return result.fun, optimal_parameters
ansatz = RealAmplitudes(4, entanglement='linear')
optimizer = GradientDescent(maxiter=50)
np.random.seed(12) # for reproducibility
fopt, optimal_parameters = layerwise_training(ansatz, 4, optimizer)
我们可以看到,随着电路深度的增加,损失函数向-1方向减小,所以我们看不到任何贫瘠的高原。