不支持的梯度配置

设备雅可比

为了在 QNode 中使用 diff_method="device",传入 QNode 构造函数的设备必须将其 "provides_jacobian" 能力设置为 True,并且必须包含一个返回每个量子电路梯度的方法 jacobian(circuits, **kwargs)。这在 default.qubit 设备中未实现,因为该设备不提供这样的 jacobian 方法(而是允许反向传播工作)。

请参阅自定义插件页面以获取更多详细信息。

如果使用此配置,将会引发一个异常:

def print_grad():
    dev = qml.device('default.qubit', wires=1, shots=None)

    @qml.qnode(dev, diff_method='device')
    def circuit(x):
        qml.RX(x[0], wires=0)
        return qml.expval(qml.Z(wires=0))

    x = np.array([0.1], requires_grad=True)
    print(qml.grad(circuit)(x))
>>> print_grad()
Traceback (most recent call last):
  ...
  File "C:\pennylane\pennylane\qnode.py", line 448, in _validate_device_method
    raise qml.QuantumFunctionError(
pennylane.QuantumFunctionError: The default.qubit device does not provide a native method for computing the jacobian.

反向传播

反向传播算法本质上是解析的,因此当使用 diff_method="backprop" 时,传递 shots=None 是唯一支持的配置。尽管即使在 shots>0 的情况下(如伴随微分的情况,见下一节),始终使用解析梯度是可能的,但在当前代码的状态下,这会破坏其他内容。

目前如果使用此无效配置,将会引发异常:

def print_grad():
    dev = qml.device('default.qubit', wires=1, shots=100)

    @qml.qnode(dev, diff_method='backprop')
    def circuit(x):
        qml.RX(x[0], wires=0)
        return qml.expval(qml.Z(wires=0))

    x = np.array([0.1], requires_grad=True)
    print(qml.grad(circuit)(x))
>>> print_grad()
Traceback (most recent call last):
  ...
  File "C:\pennylane\pennylane\qnode.py", line 375, in _validate_backprop_method
    raise qml.QuantumFunctionError("Backpropagation is only supported when shots=None.")
pennylane.QuantumFunctionError: Backpropagation is only supported when shots=None.

更改为 shots=None 允许计算解析梯度:

def print_grad():
    dev = qml.device('default.qubit', wires=1, shots=None)

    @qml.qnode(dev, diff_method='backprop')
    def circuit(x):
        qml.RX(x[0], wires=0)
        return qml.expval(qml.Z(wires=0))

    x = np.array([0.1], requires_grad=True)
    print(qml.grad(circuit)(x))
>>> print_grad()
[-0.09983342]

伴随微分

PennyLane 实现了来自 2009.02823 的伴随微分方法,该方法仅讨论可观察量期望值的梯度。

特别地,以下代码按预期工作:

def print_grad():
    dev = qml.device('default.qubit', wires=1, shots=None)

    @qml.qnode(dev, diff_method='adjoint')
    def circuit(x):
        qml.RX(x[0], wires=0)
        return qml.expval(qml.Z(wires=0))

    x = np.array([0.1], requires_grad=True)
    print(qml.grad(circuit)(x))
>>> print_grad()
[-0.09983342]

default.qubit 可以区分任何其他测量过程,只要它在 Z 测量基中。在这种情况下,我们建议使用设备提供的 vjp (device_vjp=True) 以改善性能扩展。当最终成本函数只有一个标量值时,这个算法效果最佳。

lightning.qubit 只支持期望值。

@qml.qnode(qml.device('default.qubit'), diff_method="adjoint", device_vjp=True)
def circuit(x):
    qml.IsingXX(x, wires=(0,1))
    return qml.probs(wires=(0,1))

def cost(x):
    probs = circuit(x)
    target = np.array([0, 0, 0, 1])
    return qml.math.norm(probs-target)
>>> qml.grad(cost)(qml.numpy.array(0.1))
-0.07059288589999416

此外,伴随微分算法本质上是解析的。如果执行中有 shots>0,则会引发错误:

def print_grad_ok():
    dev = qml.device('default.qubit', wires=1, shots=100)

    @qml.qnode(dev, diff_method='adjoint')
    def circuit(x):
        qml.RX(x[0], wires=0)
        return qml.expval(qml.Z(wires=0))

    x = np.array([0.1], requires_grad=True)
    print(qml.grad(circuit)(x))
>>> print_grad_ok()
DeviceError: Finite shots are not supported with adjoint + default.qubit

状态梯度

一般来说,量子电路的状态将是复数值的,因此在不使用 复分析的情况下直接对状态进行微分是不可能的。尽管大多数“简单”函数可以实现复数梯度,但在Autograd中不支持,而是在其他三个接口中完成。

相反,在Autograd中,应对输出状态进行实数标量值后处理,以允许自动微分框架进行反向传播。例如,以下代码使用一个依赖于输出状态的标量成本函数:

def state_scalar_grad():
    dev = qml.device('default.qubit', wires=1, shots=None)

    @qml.qnode(dev, diff_method='backprop')
    def circuit(x):
        qml.RX(x[0], wires=0)
        return qml.state()

    def cost_fn(x):
        out = circuit(x)
        return np.abs(out[0])

    x = np.array([0.1], requires_grad=True)
    print(qml.grad(cost_fn)(x))
>>> state_scalar_grad()
[-0.02498958]

然而,从对标量成本进行微分转变为直接对状态进行微分将会导致错误:

def state_vector_grad():
    dev = qml.device('default.qubit', wires=1, shots=None)

    @qml.qnode(dev, diff_method='backprop')
    def circuit(x):
        qml.RX(x[0], wires=0)
        return qml.state()

    x = np.array([0.1], requires_grad=True)
    print(qml.jacobian(circuit)(x))
>>> state_vector_grad()
Traceback (most recent call last):
  ...
  File "C:\Python38\lib\site-packages\numpy\core\fromnumeric.py", line 57, in _wrapfunc
    return bound(*args, **kwds)
ValueError: cannot reshape array of size 4 into shape (2,1)

使用支持复合微分的不同接口将修复此错误:

def state_vector_grad_jax():
    dev = qml.device('default.qubit', wires=1, shots=None)

    @qml.qnode(dev, interface='jax', diff_method='backprop')
    def circuit(x):
        qml.RX(x[0], wires=0)
        return qml.state()

    x = jnp.array([0.1], dtype=np.complex64)
    print(jax.jacrev(circuit, holomorphic=True)(x))

def state_vector_grad_tf():
    dev = qml.device('default.qubit', wires=1, shots=None)

    @qml.qnode(dev, interface='tf', diff_method='backprop')
    def circuit(x):
        qml.RX(x[0], wires=0)
        return qml.state()

    x = tf.Variable([0.1], trainable=True, dtype=np.complex64)
    with tf.GradientTape() as tape:
        out = circuit(x)

    print(tape.jacobian(out, [x]))

def state_vector_grad_torch():
    dev = qml.device('default.qubit', wires=1, shots=None)

    @qml.qnode(dev, interface='torch', diff_method='backprop')
    def circuit(x):
        qml.RX(x[0], wires=0)
        return qml.state()

    x = torch.tensor([0.1], requires_grad=True, dtype=torch.complex64)
    print(torch.autograd.functional.jacobian(circuit, (x,)))
>>> state_vector_grad_jax()
[[-0.02498958+0.j        ]
 [ 0.        -0.49937513j]]
>>> state_vector_grad_tf()
[<tf.Tensor: shape=(2, 1), dtype=complex64, numpy=
array([[-0.02498958+0.j        ],
       [-0.        +0.49937513j]], dtype=complex64)>]
>>> state_vector_grad_torch()
(tensor([[-0.0250+0.0000j],
        [ 0.0000+0.4994j]]),)

示例梯度

在Pennylane中,样本是从可观测量的特征值中抽取的,如果没有提供可观测量,则从计算基态中抽取。这个过程通常是不可微分的,因此不允许通过采样进行梯度反向传播。

目前,在这种情况下尝试计算梯度不会引发错误,但结果将是不正确的:

def sample_backward():
    dev = qml.device('default.qubit', wires=1, shots=20)

    @qml.qnode(dev)
    def circuit(x):
        qml.RX(x[0], wires=0)
        return qml.sample(wires=0)

    x = np.array([np.pi / 2])
    print(qml.jacobian(circuit)(x))
>>> sample_backward()
[[0.5]
 [0.5]
 [0.5]
 [0.5]
 [0.5]
 [0.5]
 [0.5]
 [0.5]
 [0.5]
 [0.5]
 [0.5]
 [0.5]
 [0.5]
 [0.5]
 [0.5]
 [0.5]
 [0.5]
 [0.5]
 [0.5]
 [0.5]]

前向传播得到支持,并将按预期运行:

def sample_forward():
    dev = qml.device('default.qubit', wires=1, shots=20)

    @qml.qnode(dev)
    def circuit(x):
        qml.RX(x[0], wires=0)
        return qml.sample(wires=0)

    x = np.array([np.pi / 2])
    print(circuit(x))
>>> sample_forward()
[0 1 0 0 0 1 1 0 0 1 1 1 0 0 0 1 1 0 0 0]