使用 ArcQML 构建量子神经网络分类器

运行前请准备 ArcQML 源码仓库及对应示例数据。下文命令和 examples/ 路径均以该仓库为基准;本文展示关键代码片段,完整程序位于文中标注的示例路径。

本教程使用 ArcQML 原生 Rust API 构建量子神经网络(QNN),对德国信用数据集执行二分类。任务定义与 benchmarks/qnn 保持一致:10 个输入特征、10 个量子比特、每层 37 个参数、测量 Z(q5),并使用二元交叉熵和 Adam 训练。

完整程序位于 examples/rust/qnn_german_credit.rs

rust
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 个离散特征:

  1. Account Balance
  2. Payment Status of Previous Credit
  3. Purpose
  4. Value Savings/Stocks
  5. Length of current employment
  6. Guarantors
  7. Most valuable available asset
  8. Concurrent Credits
  9. Type of apartment
  10. 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 缩放:

x=π2+xxminxmaxxminπ

因此每个特征被映射到 [π/2, 3π/2],可直接作为旋转角。原始 Creditability1 表示信用良好、0 表示信用不良;示例将信用不良映射为正类 1

rust
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,同时避免把输入角登记成可训练参数,我们直接构造等价初始态。

单量子比特编码态为:

RZ(x)RX(x)|0=eix/2cos(x/2)|0ieix/2sin(x/2)|1

将 10 个局部态做张量积,得到长度 2¹⁰ = 1024 的状态向量。整个 batch 使用行主序 Tensor,shape 为 [batch_size, 1024]

rust
let states = Tensor::new(TensorData::FlatC64 {
    data: amplitudes,
    shape: vec![samples.len(), STATE_DIMENSION],
})?; /* 完整代码过长,仅展示核心代码 */

let simulator = BatchStateVectorSimulator::from_state_tensor(
    QUBITS,
    states,
)?;

构造张量积时按 q9q0 迭代,使扁平振幅索引保持 ArcQML 的 little-endian 约定。

构造 37 参数 ansatz 层

每层由三段逐步向线路中心收缩的旋转和纠缠结构组成,总计 37 个参数、52 个门。

第一段在所有量子比特上添加 RX,然后从两侧建立纠缠:

rust
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…q8q2…q7 上继续添加 RX 与 CNOT,最后在中心 q3…q6 使用 RY/RZ 和纠缠门。完整层由:

rust
append_ansatz_layer(
    &mut circuit,
    &layer_parameters,
)?;

添加到线路。创建线路时使用确定性标准正态初值:

rust
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 期望值:

rust
let observable = SparsePauliOp::single(
    QUBITS,
    5usize,
    Pauli::Z,
    1.0,
)?;

模型输出为:

zi=ψ(xi,θ)|Z5|ψ(xi,θ)

zᵢ ∈ [-1, 1] 被直接当作二分类 logit。预测概率可解释为 σ(zᵢ),以 logit 0 分类等价于以概率 0.5 分类。

Batch 前向与反向传播

一个 batch 的训练函数只需要四步:创建 batch 模拟器、执行线路、计算损失、反向传播。

rust
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 训练

rust
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(),避免构建自动微分上下文:

rust
let _guard = no_grad();
let logits = simulator.run(
    &circuit,
    &observable,
)?;

示例每轮输出训练损失、验证损失和验证准确率,最后计算与基准相同的 ROC-AUC 和 PR-AUC:

text
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 阈值;生产应用可在验证阶段加入相同步骤。

运行

在仓库根目录执行:

bash
cargo run --release -p arcqml --example qnn_german_credit

10 量子比特 batch 状态的内存与计算量明显高于单样本;首次尝试建议保持 1 层。