接下来,我们通过一个最小的可运行示例,完整地展现一个量子神经网络(Quantum Neural Network, QNN)从定义电路、训练到可视化的全过程。示例任务与正文相同:拟合一维函数 ,其中 。文末我们还会给出这个小电路输出表达式的解析推导,说明它为什么有能力精确拟合

1. 准备:设备、拟设电路与 QNode

我们使用两个量子比特的默认状态向量模拟器。电路分为三层:编码层把经典数据 作为角度编码到第 0 个量子比特上;模型层由可学习参数控制的旋转门和一个 CNOT 组成;测量层返回第 0 个量子比特上 PauliZ 的期望值

import pennylane as qml
from pennylane import numpy as np
import matplotlib.pyplot as plt
 
# 设备:2 个量子比特的默认模拟器
dev = qml.device("default.qubit", wires=2)
 
def qnn_ansatz(params, x):
    """编码层 + 模型层 (Ansatz)"""
    # 编码层:把经典数据 x 作为角度编码到第 0 个量子比特
    qml.RY(x, wires=0)
    # 模型层:由可学习参数 params 控制的旋转门与纠缠门
    qml.RX(params[0], wires=0)
    qml.RY(params[1], wires=0)
    qml.CNOT(wires=[0, 1])
    qml.RX(params[2], wires=1)
    qml.RY(params[3], wires=1)
 
@qml.qnode(dev)
def quantum_neural_net(params, x):
    qnn_ansatz(params, x)
    # 测量层:返回第 0 个量子比特上 PauliZ 的期望值
    return qml.expval(qml.PauliZ(0))
 
def square_loss(labels, predictions):
    """均方误差损失"""
    return np.mean((labels - predictions) ** 2)

2. 训练

我们在 上取 20 个训练点,随机初始化 4 个可学习参数,然后用 Adam 优化器训练 50 轮。每一步优化器都会通过参数平移法则计算解析梯度;同时我们把每轮更新后的参数快照保存下来,便于稍后回看电路。

# 训练数据:在 [0, 2π] 上取 20 个点
X_train = np.linspace(0, 2 * np.pi, 20)
Y_train = np.sin(X_train)
 
# 随机初始化 QNN 的 4 个可学习参数
num_params = 4
params = np.random.uniform(0, 2 * np.pi, num_params, requires_grad=True)
 
# 优化器:Adam,学习率 0.1
opt = qml.AdamOptimizer(stepsize=0.1)
 
# 保存参数历史
params_history = []
 
epochs = 50
for i in range(epochs):
    # 优化器需要一个"只接受参数、返回损失"的函数
    def cost_fn(p):
        predictions = np.array([quantum_neural_net(p, x) for x in X_train])
        return square_loss(Y_train, predictions)
 
    # step_and_cost 内部用参数平移法则计算解析梯度并更新参数
    params, cost = opt.step_and_cost(cost_fn, params)
    params_history.append(np.array(params, dtype=float))
 
    if (i + 1) % 10 == 0:
        print(f"Epoch {i+1:2d}: Cost = {cost:.6f}")
 
print("\n训练完成! 最终参数:", params)
print("一共保存的参数快照数量:", len(params_history))

运行这段代码,你会看到损失值随迭代逐渐下降。在多数随机初始化下,损失会降到接近零——这个现象的原因在第 5 节中给出解析解释:存在使损失严格为零的参数组合。

3. 可视化拟合结果

训练结束后,我们在更细的网格上检验模型的拟合效果。

# 在更细的网格上检验拟合效果
X_test = np.linspace(0, 2 * np.pi, 100)
predictions_test = [quantum_neural_net(params, x) for x in X_test]
 
plt.figure()
plt.plot(X_train, Y_train, 'bo', label="Training data")
plt.plot(X_test, predictions_test, 'r-', label="QNN predictions")
plt.legend()
plt.title("QNN Fit to sin(x)")
plt.show()

红色曲线是 QNN 的预测。由于电路输出 天然落在 区间内,与 的值域一致,模型无需任何输出缩放即可匹配目标函数的幅度。

4. 线路可视化

下面我们单独定义一个结构相同的 QNode,用 PennyLane 自带的绘图工具查看训练前后的电路。

# 单独为可视化定义一个 QNode(结构相同)
vis_dev = qml.device("default.qubit", wires=2)
 
@qml.qnode(vis_dev)
def vis_circuit(params, x):
    qnn_ansatz(params, x)
    return qml.expval(qml.PauliZ(0))
 
def show_circuit_ascii(params, x):
    drawer = qml.draw(vis_circuit)
    print(drawer(params, x))
 
def show_circuit_mpl(params, x):
    fig, ax = qml.draw_mpl(vis_circuit)(params, x)
    ax.set_title("QNN Circuit")
    plt.tight_layout()
    plt.show()
 
# 示例:用最终参数画线路
x_example = X_train[0]
print("\nASCII 线路图:")
show_circuit_ascii(params, x_example)
 
print("\n显示 matplotlib 线路图...")
show_circuit_mpl(params, x_example)
 
# 示例:查看第 1 个 epoch 更新后的参数下的线路
first_epoch_params = params_history[0]
print("\n第 1 个 epoch 的 ASCII 线路图:")
show_circuit_ascii(first_epoch_params, x_example)

5. 为什么这个小电路足以拟合 sin(x):解析验证

这一节我们把电路的输出显式地算出来。结论是:输出只依赖参数 ,且恰为 的线性组合;取 时,它与 严格相等。因此,零损失的全局最优解是可达的,训练确实”有解可学”。

第一步:化简测量。 我们说明 CNOT 以及作用在第 1 个量子比特上的 都不影响 。首先,作用在不同量子比特上的算符显然对易,故 对易。其次,对 ,利用 是对角的且 ,可得

与 CNOT 也对易。于是在海森堡绘景中把测量算符 向后移过这三个门, 就等于它在 CNOT 之前的值;而那时第 1 个量子比特仍处于 ,其归一化因子为 。最终

这一步也解释了一个现象: 完全不出现在输出中,因此它们对损失的梯度恒为零,训练过程中这两个参数基本停留在初始值附近。

第二步:用布洛赫球跟踪态矢量。 的布洛赫矢量为 。算符 把布洛赫矢量绕 轴旋转角 (右手法则),我们依次应用三次旋转:

  1. 轴转
  2. 轴转 ):
  3. 轴转 )。

布洛赫矢量的 分量就是 ,因此

第三步:验证模型能精确表示 上式是 的线性组合,取 (此时 ),得

与目标函数严格相等,且 (以及 )可以任取。所以在参数空间中存在损失严格为零的全局最优点,训练的任务只是把 学到 (模 )。此外,模型输出关于 为周期,与 的周期一致,这使得训练区间上学到的拟合可以自然地周期延拓。

作为交叉验证,我们代入两个具体数值检验上式。取 :公式给出 ;直接演算态矢量也得到 (态先后经 后布洛赫矢量转到 )。再取 :公式给出 ,即输出恒为零;直接演算可知此时态的布洛赫矢量始终落在赤道面上,。两条路径完全一致。


参考资料