# 构建量子神经网络分类器

本教程使用 ArcQML Rust API 构建量子神经网络（QNN），对德国信用数据集执行二分类。任务定义为：10 个输入特征、10 个量子比特、每层 37 个参数、测量 `Z(q5)`，并使用二元交叉熵和 Adam 训练。

完整程序位于 [`examples/rust/qnn_german_credit.rs`](../../../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`](../../../examples/data/german_credit.csv)，共 1000 个样本。选取相同的 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' = \frac{\pi}{2} + \frac{x-x_{\min}}{x_{\max}-x_{\min}}\pi.
$$

因此每个特征被映射到 `[π/2, 3π/2]`，可直接作为旋转角。原始 `Creditability` 中 `1` 表示信用良好、`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，同时避免把输入角登记成可训练参数，我们直接构造等价初始态。

单量子比特编码态为：

$$
R_Z(x)R_X(x)|0\rangle =
e^{-ix/2}\cos(x/2)|0\rangle
-i e^{ix/2}\sin(x/2)|1\rangle.
$$

将 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,
)?;
```

构造张量积时按 `q9` 到 `q0` 迭代，使扁平振幅索引保持 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…q8` 和 `q2…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,
)?;
```

模型输出为：

$$
z_i = \langle\psi(x_i,\theta)|Z_5|\psi(x_i,\theta)\rangle.
$$

`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
cd PATH_TO_YOUR_FILES/ArcQML
export ARCQML_RUNTIME_LIB_DIR="PATH_TO_YOUR_FILES/ArcQML/libs/YOUR_DEVICES"
cargo run --release -p arcqml --example qnn_german_credit
```

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