损失函数与优化器
适用版本 · ArcQML 0.1.0
本页目录
损失函数:输入约束与前向公式
| 函数 | 关键约束 |
|---|---|
mse_loss |
prediction 与 target 非空、同 dtype(F32/F64),形状可广播。 |
l1_loss |
prediction 与 target 非空、同 dtype(F32/F64),形状可广播。 |
binary_nll_loss |
prediction 与 labels 均为一维且长度相同;标签只能取 0 或 1。 |
BCE with logits |
logits 与 targets 的 shape 和 dtype 相同;target 取值位于 0 到 1 之间。 |
cross_entropy_loss |
logits 为二维 Tensor;labels 长度等于 batch 大小,每个标签都是合法类别索引。 |
这些损失依赖当前 CPU Dense 数值算子。设 N 为广播后误差 Tensor 的元素总数,prediction 和 target 的第 i 个广播后元素分别记为预测值与目标值。例如 [B, C] 误差的 N = B × C。均方误差损失定义为:
L1 损失定义为:
binary_nll_loss 的 prediction 表示 Pauli-Z 期望值,而不是普通概率或 logit。框架先对期望值执行截断:
随后,将截断后的期望值转换为两个类别的概率:
对应的二分类负对数似然损失为:
直接接收 logits 的二元交叉熵采用数值稳定形式,其中 N 为 logits 的元素总数:
对于包含 B 个样本和 C 个类别的多分类 logits,交叉熵损失定义为:
优化器
SGD:实现是带耦合 L2 项的更新
Sgd::step 对 trainable 且 requires_grad、并且已有 grad 的 Parameter 更新。对于 F32 或 F64,每个元素执行以下更新:
无梯度参数计入 skipped_no_grad,冻结或 requires_grad=false 的参数计入 skipped_frozen。
Adam:偏置校正与权重衰减位置
Adam 在首次 step 时按参数切片中的位置建立状态槽位,后续调用必须保持参数数量和顺序不变。优化器保存迭代步数、一阶矩和二阶矩。每次更新时,首先把耦合式 L2 项加入当前梯度:
随后更新一阶矩和二阶矩:
完成偏置校正后,再更新参数:
因此,这里的 weight_decay 会进入矩估计,不等同于 AdamW 的解耦权重衰减。
构造器要求 learning_rate、weight_decay 为有限非负数,beta1 和 beta2 位于 [0, 1),epsilon 为有限正数。训练中必须复用同一个 Adam 实例;重新创建会丢失动量与步数。
推荐训练循环
let mut optimizer = Adam::new(0.01, 0.9, 0.999, 1e-8, 0.0)?;
for _ in 0..steps {
let prediction = simulator.run(&circuit, &observable)?;
let loss = mse_loss(&prediction, &target)?;
loss.backward()?;
optimizer.step(circuit.parameters())?;
optimizer.zero_grad(circuit.parameters());
}
建议在每轮 optimizer.step 之后调用 zero_grad,清空当前梯度,避免下一轮意外累积。不要在 backward 之前替换或原地修改参与前向计算的 Parameter Tensor,否则版本检查可能报告前向数据已被修改。参数更新应交给 optimizer.step 完成。
自定义损失
Rust 用户可以将量子输出继续接到 sub、square、mul、mean 等可微算子,构造自己的标量损失。普通算子组合无需手写反向;需要为图外计算或整体表达式指定导数时,可使用 CustomOp 与 apply_custom_op。自动微分遵循实际计算图的局部反向规则,不会对任意等价数学表达式自动进行符号化简。
完整的输入契约、加权 MSE、batch 电路训练、解析/数值梯度检查和专用反向示例见自定义损失教程(发行包内 docs/tutorial/custom_loss.md)与自定义损失示例(发行包内 rust/custom_loss.rs)。当前 Python 尚未导出对应的通用算子组合与 CustomOp 接口。