让我们将变分量子算法的理论付诸实践。在本节中,我们将使用 Python 和 Pennylane 库实现一个变分量子分类器 (VQC)。这个动手练习结合了本章前面讨论的想法:设计参数化量子电路 (PQC)、根据测量结果定义合适的成本函数,以及运用基于梯度的优化技术来训练电路参数以完成分类任务。我们的目标是训练一个量子电路,使其能对二维特征空间中属于两个不同类别的点进行分类。我们将通过迭代调整 PQC 参数来运用变分原理,以使量化分类错误的成本函数达到最小。1. 环境和数据准备首先,确保您已安装所需的库。您主要需要 Pennylane 来进行量子计算,以及 NumPy 来进行数值操作。Scikit-learn 对于生成样本数据以及可能的数据预处理很有用。# 必要导入 import pennylane as qml from pennylane import numpy as np # 使用 PennyLane 封装的 NumPy import matplotlib.pyplot as plt from sklearn.datasets import make_moons from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler # 生成一个合成数据集 X, y = make_moons(n_samples=100, noise=0.15, random_state=42) # 对特征进行缩放以提升性能 scaler = StandardScaler() X_scaled = scaler.fit_transform(X) # 为方便一些成本函数处理,将标签从 {0, 1} 转换为 {-1, 1} y_signed = 2 * y - 1 # 划分为训练集和测试集 X_train, X_test, y_train, y_test = train_test_split( X_scaled, y_signed, test_size=0.3, random_state=42 ) print(f"Number of training samples: {len(X_train)}") print(f"Number of testing samples: {len(X_test)}") print(f"Data shape: {X_train.shape}") # 针对 2 个特征,形状应为 (n_samples, 2)我们使用 make_moons 来生成一个非线性可分数据集,这提供了一个中等难度的任务,适合展示 VQC 的效用。数据经过缩放,标签被转换为 $y \in {-1, 1}$,这简化了将测量结果(通常在 [-1, 1] 范围内)与目标标签对齐的操作。{ "data": [ { "x": [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 0.15, 0.25, 0.35, 0.45, 0.55, 0.65, 0.75, 0.85, 0.95, 1.05], "y": [0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.0, 0.85, 0.75, 0.65, 0.55, 0.45, 0.35, 0.25, 0.15, 0.05, -0.05], "mode": "markers", "type": "scatter", "name": "类别 -1", "marker": {"color": "#4263eb", "size": 8} }, { "x": [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 0.05, 0.15, 0.25, 0.35, 0.45, 0.55, 0.65, 0.75, 0.85, 0.95], "y": [-0.05, 0.05, 0.15, 0.25, 0.35, 0.45, 0.55, 0.65, 0.75, 0.85, -0.1, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8], "mode": "markers", "type": "scatter", "name": "类别 +1", "marker": {"color": "#f76707", "size": 8} } ], "layout": { "title": {"text": "缩放后的合成月亮数据集"}, "xaxis": {"title": {"text": "特征 1 (缩放后)"}}, "yaxis": {"title": {"text": "特征 2 (缩放后)"}}, "width": 600, "height": 400, "plot_bgcolor": "#e9ecef" } }经过特征缩放后的合成“月亮”数据集。这两个类别明显是非线性可分的。2. 量子数据编码我们需要将经典数据点 $x = (x_1, x_2)$ 编码为量子态。如第二章所述,存在多种策略。在这里,我们将使用角度编码,将每个特征映射到特定门的旋转角度。由于我们有 2 个特征,我们将使用 2 个量子位。num_qubits = 2 def data_encoding_circuit(features): """将 2 个特征编码为 2 个量子位的状态。""" qml.AngleEmbedding(features, wires=range(num_qubits), rotation='Y') # 如果需要,您可以在此处添加纠缠,例如:qml.CNOT(wires=[0, 1]) # 但目前我们保持编码简单。此函数使用 qml.AngleEmbedding 对每个量子位施加 $R_Y(\phi)$ 旋转,其中 $\phi$ 是对应的缩放特征值。这是一种常用且相对简单的编码方法。3. 参数化量子电路(Ansatz)设计现在,我们设计核心 PQC 或 ansatz,我们将对其参数 $\theta$ 进行优化。我们将使用一个包含单量子位旋转层和纠缠 CNOT 门构成的结构。这遵循了前面讨论的 PQC 设计策略(第 4.2 节)。num_layers = 3 # ansatz 中的层数 def ansatz(params): """参数化量子电路 (ansatz)。""" for l in range(num_layers): # 单量子位旋转层(参数化) for i in range(num_qubits): qml.RX(params[l, i, 0], wires=i) qml.RZ(params[l, i, 1], wires=i) # 纠缠门层 for i in range(num_qubits - 1): qml.CNOT(wires=[i, i+1]) # 如果需要更多纠缠,可在最后一个和第一个量子位之间添加一个 CNOT if num_qubits > 1: qml.CNOT(wires=[num_qubits-1, 0])这个 ansatz 由 num_layers 个模块组成。每个模块对所有量子位施加参数化的 $RX$ 和 $RZ$ 旋转,然后是一系列 CNOT 门以引入纠缠。params 数组的形状将取决于 num_layers、num_qubits 以及每层每个门参数的数量(此处为每层每个量子位 2 个参数)。4. 定义量子节点和成本函数我们将数据编码和 ansatz 结合成一个完整的量子电路。测量结果将是第一个量子位上泡利-Z算符的期望值 $\langle \sigma_z^{(0)} \rangle$。该值介于 -1 和 1 之间,与我们的标签 $y \in {-1, 1}$ 自然吻合。我们定义一个量子设备(此处为模拟器),并创建一个 QNode,它封装了量子电路逻辑。# 选择一个量子设备(模拟器) dev = qml.device("default.qubit", wires=num_qubits) @qml.qnode(dev) def quantum_classifier_circuit(params, features): """完整量子电路:编码 + ansatz + 测量。""" data_encoding_circuit(features) ansatz(params) # 测量第一个量子位上泡利 Z 的期望值 return qml.expval(qml.PauliZ(0))接下来,我们定义成本函数(第 4.3 节)。对于标签为 ${-1, 1}$ 且预测结果 $p \in [-1, 1]$ 的二分类问题,常用的选择是平方损失:$L(\theta) = \frac{1}{N} \sum_{i=1}^{N} (y_i - p_i(\theta))^2$,其中 $p_i(\theta)$ 是量子电路对输入 $x_i$ 在参数 $\theta$ 下的输出。def square_loss(labels, predictions): """计算均方损失。""" return np.mean((labels - predictions)**2) def cost_function(params, features_batch, labels_batch): """待最小化的成本函数。""" predictions = [quantum_classifier_circuit(params, f) for f in features_batch] return square_loss(labels_batch, predictions)这个成本函数接收一批特征和标签,使用当前 params 为每个特征向量通过 quantum_classifier_circuit 计算预测值,然后计算均方误差。5. 优化过程我们需要一个优化器来根据成本函数的梯度更新电路参数 $\theta$。Pennylane 可以与 Adam 等优化器(第 4.5 节)结合使用,并且默认情况下,对于兼容的门,它会自动使用参数偏移规则(第 4.4 节)等方法计算梯度。# 随机初始化参数 param_shape = (num_layers, num_qubits, 2) # 由我们的 ansatz 结构定义 initial_params = np.random.uniform(low=0, high=2 * np.pi, size=param_shape) # 选择一个优化器 optimizer = qml.AdamOptimizer(stepsize=0.05) # 训练参数 epochs = 30 batch_size = 10 # 训练循环 params = initial_params cost_history = [] print("开始训练...") for epoch in range(epochs): # 创建批次 permutation = np.random.permutation(len(X_train)) X_train_perm = X_train[permutation] y_train_perm = y_train[permutation] batch_costs = [] for i in range(0, len(X_train), batch_size): X_batch = X_train_perm[i : i + batch_size] y_batch = y_train_perm[i : i + batch_size] # 梯度下降步骤 params, current_cost = optimizer.step_and_cost( lambda p: cost_function(p, X_batch, y_batch), params ) batch_costs.append(current_cost) epoch_cost = np.mean(batch_costs) cost_history.append(epoch_cost) # 可选:在训练期间计算训练集上的准确率 predictions_train = [np.sign(quantum_classifier_circuit(params, f)) for f in X_train] accuracy_train = np.mean(predictions_train == y_train) print(f"Epoch {epoch+1}/{epochs} - Cost: {epoch_cost:.4f} - Train Accuracy: {accuracy_train:.4f}") print("训练完成。")在这个循环中,我们遍历训练轮次 (epochs),打乱数据,分批处理,并使用优化器的 step_and_cost 方法。该方法隐含地计算成本及其相对于 params 的梯度(在幕后使用参数偏移规则),并根据 Adam 优化算法更新 params。我们记录每个训练轮次的成本。6. 评估训练结束后,我们评估 VQC 在未见过测试集上的性能。我们使用最终训练好的参数 (params) 来预测测试数据的标签并计算准确率。# 在测试集上进行预测 predictions_test = [np.sign(quantum_classifier_circuit(params, f)) for f in X_test] # 计算测试准确率 accuracy_test = np.mean(predictions_test == y_test) print(f"\nTest Set Accuracy: {accuracy_test:.4f}") # 可选:绘制成本历史曲线 plt.figure(figsize=(8, 4)) plt.plot(range(1, epochs + 1), cost_history) plt.xlabel("Epoch") plt.ylabel("Cost (Mean Squared Loss)") plt.title("VQC Training Cost History") plt.grid(True, linestyle='--', alpha=0.6) plt.show(){ "data": [ { "y": [0.8, 0.6, 0.45, 0.3, 0.2, 0.15, 0.1, 0.08, 0.06, 0.05, 0.04, 0.035, 0.03, 0.028, 0.025, 0.023, 0.021, 0.02, 0.019, 0.018], "type": "scatter", "mode": "lines+markers", "name": "成本", "marker": {"color": "#1c7ed6"} } ], "layout": { "title": {"text": "VQC 训练成本历史"}, "xaxis": {"title": {"text": "训练轮次"}}, "yaxis": {"title": {"text": "成本(均方损失)"}}, "width": 600, "height": 400, "plot_bgcolor": "#e9ecef" } }示例训练成本曲线,显示了均方损失随训练轮次的减少情况。我们还可以可视化分类器学习到的决策边界。这包括创建一个覆盖特征空间点的网格,使用训练好的 VQC 预测每个点的类别,并绘制结果。# 为绘制决策边界创建网格 x_min, x_max = X_scaled[:, 0].min() - 0.5, X_scaled[:, 0].max() + 0.5 y_min, y_max = X_scaled[:, 1].min() - 0.5, X_scaled[:, 1].max() + 0.5 xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.05), np.arange(y_min, y_max, 0.05)) # 预测网格中每个点的类别 grid_points = np.c_[xx.ravel(), yy.ravel()] Z = np.array([quantum_classifier_circuit(params, p) for p in grid_points]) Z = Z.reshape(xx.shape) # 预测值(-1 到 1) # 绘制决策边界和数据点 plt.figure(figsize=(8, 6)) contour = plt.contourf(xx, yy, Z, levels=np.linspace(-1, 1, 3), cmap='coolwarm', alpha=0.8) plt.colorbar(contour, ticks=[-1, 0, 1]) # 绘制训练数据 plt.scatter(X_train[:, 0], X_train[:, 1], c=y_train, cmap='coolwarm', edgecolors='k', marker='o', s=50, label='Train Data') # 绘制测试数据 plt.scatter(X_test[:, 0], X_test[:, 1], c=y_test, cmap='coolwarm', edgecolors='k', marker='^', s=60, label='Test Data') plt.xlabel("Feature 1 (Scaled)") plt.ylabel("Feature 2 (Scaled)") plt.title("VQC Decision Boundary and Data") plt.legend() plt.grid(True, linestyle='--', alpha=0.3) plt.show()生成的图显示了 VQC 如何根据学习到的参数划分特征空间。7. 讨论本次实践练习展示了构建和训练变分量子分类器的端到端过程。我们成功实现了数据编码,设计了 PQC ansatz,定义了基于量子测量的成本函数,并使用带有参数偏移规则的梯度下降进行优化。所达到的准确率很大程度上取决于数据集的复杂性、所选 ansatz 的表达能力(层数、量子位连接性)、编码策略、优化器设置以及训练轮次数量。可能进行的下一步:尝试不同的 Ansätze: 尝试更深的电路、替代的门序列,或已知具有更高表达能力的结构(例如 qml.StronglyEntanglingLayers)。注意更深电路中出现“贫瘠高原”(第 4.7 节)的可能性。考察其他优化器: 测试 qml.QNGOptimizer(量子自然梯度,第 4.6 节)等优化器,尽管其计算量可能更大。比较收敛速度和最终准确率。修改数据编码: 使用不同的编码方法(例如,振幅编码,第二章中更高阶的特征映射),并观察对性能的影响。噪声模拟: 在有噪声的后端上运行模拟(使用 Pennylane 的噪声模拟能力),以了解硬件噪声的影响,为第七章中的想法做准备。应用误差缓解: 对有噪声的模拟结果实施基本的误差缓解技术(例如零噪声外推,第七章有讨论)。此实现为理解 VQA 提供了扎实的根基。通过修改 ansatz、优化器或成本函数等组成部分,您可以研究这些强大混合算法的设计空间。