# 量子神经网络:示例 接下来,我们通过一个最小的可运行示例,完整地展现一个量子神经网络(Quantum Neural Network, QNN)从定义电路、训练到可视化的全过程。示例任务与正文相同:拟合一维函数 $y = \sin(x)$,其中 $x \in [0, 2\pi]$。文末我们还会给出这个小电路输出表达式的解析推导,说明它为什么有能力精确拟合 $\sin(x)$。 ## 1. 准备:设备、拟设电路与 QNode 我们使用两个量子比特的默认状态向量模拟器。电路分为三层:编码层把经典数据 $x$ 作为角度编码到第 0 个量子比特上;模型层由可学习参数控制的旋转门和一个 CNOT 组成;测量层返回第 0 个量子比特上 PauliZ 的期望值。 ```python 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. 训练 我们在 $[0, 2\pi]$ 上取 20 个训练点,随机初始化 4 个可学习参数,然后用 Adam 优化器训练 50 轮。每一步优化器都会通过参数平移法则计算解析梯度;同时我们把每轮更新后的参数快照保存下来,便于稍后回看电路。 ```python # 训练数据:在 [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. 可视化拟合结果 训练结束后,我们在更细的网格上检验模型的拟合效果。 ```python # 在更细的网格上检验拟合效果 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 的预测。由于电路输出 $\langle Z_0\rangle$ 天然落在 $[-1, 1]$ 区间内,与 $\sin(x)$ 的值域一致,模型无需任何输出缩放即可匹配目标函数的幅度。 ## 4. 线路可视化 下面我们单独定义一个结构相同的 QNode,用 PennyLane 自带的绘图工具查看训练前后的电路。 ```python # 单独为可视化定义一个 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):解析验证 这一节我们把电路的输出显式地算出来。结论是:输出只依赖参数 $\theta_0, \theta_1$,且恰为 $\sin x$ 与 $\cos x$ 的线性组合;取 $\theta_1 = 3\pi/2$ 时,它与 $\sin x$ 严格相等。因此,零损失的全局最优解是可达的,训练确实"有解可学"。 **第一步:化简测量。** 我们说明 CNOT 以及作用在第 1 个量子比特上的 $R_x(\theta_2)$、$R_y(\theta_3)$ 都不影响 $\langle Z_0 \rangle$。首先,作用在不同量子比特上的算符显然对易,故 $R_x(\theta_2)$、$R_y(\theta_3)$ 与 $Z_0 = Z\otimes I$ 对易。其次,对 $\mathrm{CNOT} = |0\rangle\langle 0|\otimes I + |1\rangle\langle 1|\otimes X$,利用 $Z$ 是对角的且 $Z|0\rangle\langle 0| = |0\rangle\langle 0|$、$Z|1\rangle\langle 1| = -|1\rangle\langle 1|$,可得 $$ (Z\otimes I)\,\mathrm{CNOT} = |0\rangle\langle 0|\otimes I - |1\rangle\langle 1|\otimes X = \mathrm{CNOT}\,(Z\otimes I), $$ 即 $Z_0$ 与 CNOT 也对易。于是在海森堡绘景中把测量算符 $Z_0$ 向后移过这三个门,$\langle Z_0\rangle$ 就等于它在 CNOT 之前的值;而那时第 1 个量子比特仍处于 $|0\rangle$,其归一化因子为 $1$。最终 $$ \langle Z_0 \rangle = \langle\varphi| Z |\varphi\rangle, \qquad |\varphi\rangle = R_y(\theta_1)\, R_x(\theta_0)\, R_y(x)\,|0\rangle. $$ 这一步也解释了一个现象:$\theta_2$ 和 $\theta_3$ 完全不出现在输出中,因此它们对损失的梯度恒为零,训练过程中这两个参数基本停留在初始值附近。 **第二步:用布洛赫球跟踪态矢量。** $|0\rangle$ 的布洛赫矢量为 $(0, 0, 1)$。算符 $R_{\hat n}(\alpha) = e^{-i\alpha \hat n\cdot\vec\sigma/2}$ 把布洛赫矢量绕 $\hat n$ 轴旋转角 $\alpha$(右手法则),我们依次应用三次旋转: 1. 绕 $y$ 轴转 $x$:$(0,0,1) \mapsto (\sin x,\ 0,\ \cos x)$; 2. 绕 $x$ 轴转 $\theta_0$($y' = y\cos\theta_0 - z\sin\theta_0$,$z' = y\sin\theta_0 + z\cos\theta_0$):$(\sin x,\ 0,\ \cos x) \mapsto (\sin x,\ -\cos x\sin\theta_0,\ \cos x\cos\theta_0)$; 3. 绕 $y$ 轴转 $\theta_1$($z'' = -x\sin\theta_1 + z\cos\theta_1$)。 布洛赫矢量的 $z$ 分量就是 $\langle Z\rangle$,因此 $$ \langle Z_0 \rangle = -\sin x\,\sin\theta_1 + \cos x\,\cos\theta_0\cos\theta_1. $$ **第三步:验证模型能精确表示 $\sin x$。** 上式是 $\sin x$ 与 $\cos x$ 的线性组合,取 $\theta_1 = \frac{3\pi}{2}$(此时 $\sin\theta_1 = -1$、$\cos\theta_1 = 0$),得 $$ \langle Z_0 \rangle = \sin x, $$ 与目标函数严格相等,且 $\theta_0$(以及 $\theta_2, \theta_3$)可以任取。所以在参数空间中存在损失严格为零的全局最优点,训练的任务只是把 $\theta_1$ 学到 $\frac{3\pi}{2}$(模 $2\pi$)。此外,模型输出关于 $x$ 以 $2\pi$ 为周期,与 $\sin x$ 的周期一致,这使得训练区间上学到的拟合可以自然地周期延拓。 作为交叉验证,我们代入两个具体数值检验上式。取 $\theta_0 = 0$、$\theta_1 = \frac{\pi}{2}$、$x = \frac{\pi}{2}$:公式给出 $-\sin\frac{\pi}{2}\sin\frac{\pi}{2} + \cos\frac{\pi}{2}\cos 0\cos\frac{\pi}{2} = -1$;直接演算态矢量也得到 $\langle Z_0\rangle = -1$(态先后经 $R_y(\frac{\pi}{2})$、$R_x(0)$、$R_y(\frac{\pi}{2})$ 后布洛赫矢量转到 $(0, 0, -1)$)。再取 $\theta_0 = \frac{\pi}{2}$、$\theta_1 = 0$:公式给出 $\cos\frac{\pi}{2}\cos 0\cos x = 0$,即输出恒为零;直接演算可知此时态的布洛赫矢量始终落在赤道面上,$\langle Z_0\rangle = 0$。两条路径完全一致。