使用 ArcQML 构建量子神经网络分类器
运行前请准备 ArcQML 源码仓库及对应示例数据。下文命令和 examples/ 路径均以该仓库为基准;本文展示关键代码片段,完整程序位于文中标注的示例路径。
本教程使用 ArcQML 原生 Rust API 构建量子神经网络(QNN),对德国信用数据集执行二分类。任务定义与 benchmarks/qnn 保持一致:10 个输入特征、10 个量子比特、每层 37 个参数、测量 Z(q5),并使用二元交叉熵和 Adam 训练。
完整程序位于 examples/rust/qnn_german_credit.rs。
use arcqml::prelude::*;
use num_complex::Complex64;
arcqml::prelude 汇总各稳定功能 crate 的 prelude,Complex64 来自 num-complex,用于构造复数振幅。
任务与超参数
示例数据文件为 examples/data/german_credit.csv,共 1000 个样本。该文件与 benchmark 使用的数据一致,但教程示例只从 examples/data 读取,不依赖 benchmark 目录。选取相同的 10 个离散特征:
- Account Balance
- Payment Status of Previous Credit
- Purpose
- Value Savings/Stocks
- Length of current employment
- Guarantors
- Most valuable available asset
- Concurrent Credits
- Type of apartment
- No of Credits at this Bank
教学示例默认配置:
| 配置 | 值 |
|---|---|
| 量子比特 | 10 |
| QNN 层数 | 1 |
| 每层参数 | 37 |
| 训练/验证/测试 | 800/100/100 |
| batch size | 100 |
| epochs | 5 |
| Adam 学习率 | 0.01 |
| 随机种子 | 4 |
读取和预处理数据
示例使用 std::fs 读取 CSV,然后按列执行 min-max 缩放:
因此每个特征被映射到 [π/2, 3π/2],可直接作为旋转角。原始 Creditability 中 1 表示信用良好、0 表示信用不良;示例将信用不良映射为正类 1:
let creditability: u8 = fields[label_column].parse()?;
let label = f64::from(1 - creditability);
数据使用确定性的 Fisher–Yates shuffle 后划分为 800/100/100。完整解析与校验代码保留在可运行示例中,教程下面集中解释量子部分。
特征编码为 batch 初态
基准为每个特征 xᵢ 依次施加 RX(xᵢ) 和 RZ(xᵢ) 进行编码。为了让一条共享参数线路处理整个 batch,同时避免把输入角登记成可训练参数,我们直接构造等价初始态。
单量子比特编码态为:
将 10 个局部态做张量积,得到长度 2¹⁰ = 1024 的状态向量。整个 batch 使用行主序 Tensor,shape 为 [batch_size, 1024]:
let states = Tensor::new(TensorData::FlatC64 {
data: amplitudes,
shape: vec![samples.len(), STATE_DIMENSION],
})?; /* 完整代码过长,仅展示核心代码 */
let simulator = BatchStateVectorSimulator::from_state_tensor(
QUBITS,
states,
)?;
构造张量积时按 q9 到 q0 迭代,使扁平振幅索引保持 ArcQML 的 little-endian 约定。
构造 37 参数 ansatz 层
每层由三段逐步向线路中心收缩的旋转和纠缠结构组成,总计 37 个参数、52 个门。
第一段在所有量子比特上添加 RX,然后从两侧建立纠缠:
for qubit in 0..QUBITS {
circuit.rx(
values[qubit],
qubit,
)?;
}
for (control, target) in [(0, 1), (2, 3), (4, 5), (9, 8), (7, 6)] {
circuit.cnot(
control,
target,
)?;
}
随后在 q1…q8 和 q2…q7 上继续添加 RX 与 CNOT,最后在中心 q3…q6 使用 RY/RZ 和纠缠门。完整层由:
append_ansatz_layer(
&mut circuit,
&layer_parameters,
)?;
添加到线路。创建线路时使用确定性标准正态初值:
let mut circuit = Circuit::new(QUBITS)?;
for _layer in 0..LAYERS {
let layer_parameters: Vec<f64> = (0..PARAMETERS_PER_LAYER)
.map(|_| rng.normal())
.collect();
append_ansatz_layer(
&mut circuit,
&layer_parameters,
)?;
}
调用 circuit.rx/ry/rz 会创建可训练参数,所以 L 层线路具有 37L 个参数。
定义 QNN 输出
与基准任务一致,模型读取第 5 号量子比特的 Pauli-Z 期望值:
let observable = SparsePauliOp::single(
QUBITS,
5usize,
Pauli::Z,
1.0,
)?;
模型输出为:
zᵢ ∈ [-1, 1] 被直接当作二分类 logit。预测概率可解释为 σ(zᵢ),以 logit 0 分类等价于以概率 0.5 分类。
Batch 前向与反向传播
一个 batch 的训练函数只需要四步:创建 batch 模拟器、执行线路、计算损失、反向传播。
let states = encode_product_states( samples)?;
let simulator = BatchStateVectorSimulator::from_state_tensor(
QUBITS,
states,
)?;
let logits = simulator.run(
circuit,
observable,
)?;
let targets = labels(samples)?;
let loss = binary_cross_entropy_with_logits_loss(
&logits,
&targets,
)?;
let loss_value = loss.value()?;
loss.backward()?;
run 返回 shape [batch_size] 的 F64 Tensor。损失函数对 batch 取均值,backward 先通过 BCE 计算每个 logit 的上游梯度,再通过量子伴随算法累计所有线路参数的梯度。
使用 Adam 训练
let mut optimizer = Adam::new(
0.01,
0.9,
0.999,
1e-8,
0.0,
)?;
for epoch in 1..=EPOCHS {
rng.shuffle(&mut indices);
for batch_indices in indices.chunks(BATCH_SIZE) {
let batch = /* 根据 batch_indices 取得样本 */;
let loss = loss_and_backward(
&circuit,
&observable,
&batch,
)?;
optimizer.step(circuit.parameters())?;
optimizer.zero_grad(circuit.parameters());
}
}
zero_grad 在每次 step 后调用,防止梯度跨 batch 累积。Adam 实例必须在整个训练过程中复用,以保留一阶、二阶动量。
验证与测试
验证和测试阶段使用 no_grad(),避免构建自动微分上下文:
let _guard = no_grad();
let logits = simulator.run(
&circuit,
&observable,
)?;
示例每轮输出训练损失、验证损失和验证准确率,最后计算与基准相同的 ROC-AUC 和 PR-AUC:
samples: train=800, validation=100, test=100
qubits=10, layers=1, parameters=37
epoch 01/5: train_loss=..., validation_loss=..., validation_accuracy=...%
...
test ROC-AUC=..., PR-AUC=...
AUC 衡量的是分数排序质量,因此不需要先选择分类阈值。完整基准还会在验证集上寻找最佳 F1 阈值;生产应用可在验证阶段加入相同步骤。
运行
在仓库根目录执行:
cargo run --release -p arcqml --example qnn_german_credit
10 量子比特 batch 状态的内存与计算量明显高于单样本;首次尝试建议保持 1 层。