简介:qlstm(量子长短期记忆网络)示例项目面向量子机器学习初学者与自然语言处理开发者,提供一套基于PennyLane与PyTorch的可运行实验框架,并以词性标注任务为例,直观展示量子长短期记忆网络与经典长短期记忆网络的训练效果差异。压缩包共10个文件,仅84KB,包含3个Python脚本、1个Jupyter Notebook交互式讲解、3张训练结果对比图、2个环境依赖文本和1个Markdown说明;其中脚本覆盖数据下载、模型定义与训练入口,Notebook适合逐步跟读,图片可快速预览量子与经典模型在准确率等指标上的收敛情况。已有423人学习下载,适合希望快速搭建混合量子经典神经网络实验环境、理解量子长短期记忆网络基本工作原理并开展对比实验的读者。通过内置的example_pos.py命令即可一键启动训练,配合依赖清单与虚拟环境配置可减少环境搭建成本;数据预处理与训练结果可视化也已封装好,便于替换数据集或扩展为其他序列标注任务,让入门者更专注于模型本身。整个项目结构紧凑,按脚本、文档与结果图分层组织,下载后即可按说明直接运行。
1. qlstm 到底是什么:把 LSTM 的门控计算搬上量子线路
qlstm(量子 LSTM,Quantum Long Short-Term Memory)不是一种全新的模型家族,而是一套混合量子-经典架构:经典 LSTM 单元里最贵的四组门控矩阵乘法,被一段参数化量子线路(VQC)替换掉,记忆更新与输出逻辑仍跑在经典神经网络里。它瞄准时序建模中的非线性表达能力——同样的门控函数拟合,量子线路只用几十个变分参数加纠缠态的高维表达就能完成,而不需要堆千万级权重。最适合两类人:想验证量子优势在哪的算法工程师,和被序列建模非线性卡住、想换一种参数效率更高形式的实践者。下面按机制、实现、参数、踩坑、验证的顺序,讲一套可以直接复现的方案。
2. 量子 LSTM 的机制与选型:从矩阵乘法到参数化量子线路
2.1 经典 LSTM 的门控在哪里最费算力
LSTM 单元在每个时间步要做四组独立变换:遗忘门 f_t、输入门 i_t、输出门 o_t 和候选记忆 g_t。经典实现里每一路都是一个大矩阵乘法再激活,四路合计参数量是 4 × (input_size + hidden_size) × hidden_size。hidden_size 一上 256,单层就是几十万到上百万参数。这些参数本质上是同一种线性映射的重复堆叠,表达能力靠数量堆出来,不是靠结构创新。
量子替换的核心思路是:把"拼接 [h_{t-1}, x_t] 乘大矩阵"这一步,换成"把拼接向量编码进量子比特,跑一段带可训练旋转角参数的纠缠线路,然后读测量期望作为门控原始输出"。矩阵乘法被参数化量子线路替代,四路门的区分交给一个很小的经典线性层完成。这样做的好处是参数量从矩阵规模降到线路深度规模,同时纠缠线路提供的特征空间维度是 2^n 量级,远超同样数量经典参数能表达的函数族。
2.2 参数化量子线路替换门控计算的完整映射
常见做法是先压缩:把 input_size + hidden_size 维的拼接向量,用一个经典线性层压到 n_qubits 维。n_qubits 一般取 2 到 6,远小于拼接维度,这是有意的信息瓶颈——量子线路的处理能力由比特数决定,强行塞超过物理容量的信息只会让编码互相干扰。压缩后用 sigmoid 拉到 (0,1),乘 π 做 RY 角度编码,保证每个特征独占一个旋转角且不产生周期性混淆。
线路主体推荐三层结构:AngleEmbedding 做输入编码 → 多次变分层(单比特 RY/RZ 旋转 + CNOT 纠缠)→ PauliZ 期望测量。每个比特出一个取值在 [-1,1] 的实数,n_qubits 个实数拼成量子特征向量,再过一层经典线性层映射回 4 × hidden_size,拆成四路门并分别走 sigmoid 或 tanh。记忆状态更新公式与经典 LSTM 完全一致:c_t = f_t ⊙ c_{t-1} + i_t ⊙ g_t,h_t = o_t ⊙ tanh(c_t)。量子层只替换"计算门控输入"这一步,记忆单元的时间维梯度路径保持干净。
2.3 编码方式与测量方式怎么选:AngleEmbedding + PauliZ 的取舍
编码方式上,AngleEmbedding 的 rotation="Y" 是最稳的入门选择:一个实数映射成一个 RY 旋转角,线路深度浅,梯度顺畅回传。比它更激进的是数据重上传(data re-uploading),把同一份输入在每一层都编码一次,表达能力更强,但训练更容易陷入局部平坦区(barren plateaus),新手不建议一上来就用。Qiskit 侧则是把编码和变分层都用 QuantumCircuit 显式搭建,功能等价,只是梯度回传要借额外的桥接层或自己写参数平移(parameter-shift),门槛明显更高。
测量方式上,PauliZ 期望得到 [-1,1] 的连续值,正好接给经典线性层;如果想直接喂给 sigmoid,可以在读数后做 (expval + 1) / 2。PauliX 或 PauliY 的期望会侧重不同基下的信息,但我实测下来多数任务没本质差异,建议先用 PauliZ 把基线跑通,再考虑换测量基。测量之后不要加 Softmax 或 BatchNorm,量子输出的尺度本身是特征的一部分,破坏它会直接扰乱梯度。
2.4 为什么不用纯量子模型:混合梯度的现实边界
纯量子 RNN 在当今硬件上跑时序任务,最大障碍是噪声和采样开销:每个时间步重新编译线路、重复采样求期望,序列一长误差累积没法看。混合架构的好处是把量子部分当成一个可微、可学习的非线性特征提取器,包在经典 LSTM 外壳里——经典部分负责记忆和门控逻辑,量子部分负责高维非线性映射,梯度由经典反向传播一路传到量子参数上,训练体验和普通 PyTorch 模型几乎没有差别。
边界也要说清:在经典模拟器上,n_qubits 超过 10 后态矢量内存按 2^n 增长,训练速度断崖式下降。qlstm 真正有意义的区间是隐藏维度和序列长度都不大、且门控非线性复杂的小规模任务,或者作为量子机器学习的教学与预研样板。别指望它在普通 CPU 上吊打同等规模经典 LSTM——那是拿模拟器的软肋比经典算法的强项。评估它的正确姿势是参数效率和表达上限,不是训练速度。
3. 最小可运行实现:用 PennyLane 把 QLSTM 搭起来
3.1 环境与依赖:torch 加 pennylane 就够了
依赖非常少:PyTorch 提供自动微分和训练循环,PennyLane 提供可微量子线路和 torch 接口。安装用一行 pip 搞定。建议再装 lightning.qubit 模拟器,它在同一台机器上比默认的 default.qubit 快不少,尤其是线路稍深的时候。
pip install torch pennylane pennylane-lightning装完后先做一次性冒烟测试:定义 device,构造最小线路,跑一次前向,确认输出维度对。这个测试能提前暴露 PennyLane 与 torch 的接口不匹配问题,比直接训练再排错快得多。device 的选择会影响梯度求法:backprop 方式直接沿模拟态矢量的计算图回传,parameter-shift 则要在真实硬件上模拟采样,训练速度差几十倍,本地实验一律用 backprop。
3.2 量子线路定义:编码、变分层、测量的核心代码
这一步是整个 qlstm 最不能出错的地方。线路接收两个输入:编码向量 z(形状 [batch, n_qubits] 或 [n_qubits])和变分权重 weights(形状 [n_qlayers, n_qubits, 3])。我用 AngleEmbedding 做编码,StronglyEntanglingLayers 做变分层,最后每个比特测一个 PauliZ。
import numpy as np import pennylane as qml import torch import torch.nn as nn n_qubits = 4 # 量子比特数,也是信息瓶颈维度 n_qlayers = 2 # 变分层数,1~3 之间最稳妥 dev = qml.device("default.qubit", wires=n_qubits) @qml.qnode(dev, interface="torch", diff_method="backprop") def quantum_circuit(z, weights): # z: 已归一化到 (0,1) 的编码向量,乘 pi 折成旋转角 qml.AngleEmbedding(z * np.pi, wires=range(n_qubits), rotation="Y") # weights: 变分旋转角 + 相邻比特 CNOT 纠缠 qml.StronglyEntanglingLayers(weights, wires=range(n_qubits)) # 每比特测 PauliZ 期望,输出 n_qubits 个实数 return [qml.expval(qml.PauliZ(i)) for i in range(n_qubits)]逻辑说明:AngleEmbedding 把 n_qubits 个实数分别作用到各比特的 RY 旋转上,输入值落在 (0,1) 时旋转角在 (0, π) 内,编码可逆且不饱和。StronglyEntanglingLayers 每一层先对每比特做 RY 和 RZ 旋转,再对相邻比特施加 CNOT,末比特与首比特再连一个 CNOT 形成环形纠缠,让信息在全线路里充分混合。测量后每个比特的 PauliZ 期望接近 +1 表示该比特倾向落在 |0⟩,接近 -1 倾向 |1⟩,中间值体现叠加概率。
参数说明:weights 是线路里仅有的可训练参数,总个数 = n_qlayers × n_qubits × 3,往往只有几百个,比经典门控矩阵的参数量小一到两个数量级。z 可以带 batch 维,PennyLane 当前主线版本对 default.qubit 支持 batch 广播;如果报形状错误,退回到逐样本循环调用,功能等价只是慢一些。
3.3 QLSTM 单元封装:把量子输出接回经典门控
线路定义好之后,封装成 nn.Module 的 cell。这里有两个经典投影层:in_proj 负责把拼接向量压到 n_qubits,out_proj 负责把量子特征向量映射回 4 × hidden_size。weights 作为 nn.Parameter 直接在模块里声明,让 PyTorch 优化器自动管理它。
class QLSTMCell(nn.Module): def __init__(self, input_size, hidden_size, n_qubits=4, n_qlayers=2): super().__init__() self.hidden_size = hidden_size self.n_qubits = n_qubits # 把 [x_t, h_{t-1}] 压到 n_qubits 维,再归一化到 (0,1) self.in_proj = nn.Linear(input_size + hidden_size, n_qubits, bias=True) # 量子特征向量 -> 四路门的总输出 self.out_proj = nn.Linear(n_qubits, 4 * hidden_size, bias=True) # 量子线路的变分参数,零均值小方差初始化,避免初始饱和 self.weights = nn.Parameter(0.1 * torch.randn(n_qlayers, n_qubits, 3)) def forward(self, x_t, h_prev, c_prev): z = torch.cat([x_t, h_prev], dim=-1) # [batch, in+hidden] z = torch.sigmoid(self.in_proj(z)) # 压维并约束在 (0,1) q = quantum_circuit(z, self.weights) # [batch, n_qubits] q = (q + 1.0) / 2.0 # PauliZ 期望映射到 (0,1) gates = self.out_proj(q) # [batch, 4*hidden] f, i, g, o = torch.split(gates, self.hidden_size, dim=-1) f, i, o = torch.sigmoid(f), torch.sigmoid(i), torch.sigmoid(o) g = torch.tanh(g) c_new = f * c_prev + i * g h_new = o * torch.tanh(c_new) return h_new, c_new逻辑说明:先拼接 x_t 和上一时刻隐藏态,in_proj 压缩后用 sigmoid 归一化。这一步非常关键——RY 旋转角超出 π 后会因周期性产生输入混淆,两个不同的原始值可能映射到同一个量子态;sigmoid 把输入严格限制在 (0,1),角度落在 (0, π),编码保真。量子线路返回 [-1,1] 的期望值,统一加 1 除以 2 变成 (0,1) 区间,再交给 out_proj 分配成四路门。门控公式保持经典 LSTM 原样,这样记忆单元 c_t 的梯度路径干净连续,量子部分学到的只是特征映射,不改记忆逻辑。
参数说明:hidden_size 建议从 8 或 16 起步,n_qubits 不要大于 hidden_size。out_proj 是唯一把量子维度扩到四路门的桥梁,它的 bias 承担 sigmoid 偏置调节;训练初期如果门控偏置需要向 0 或 1 偏移,主要靠这个 bias 完成。weights 初始化为 0.1 倍标准正态,太大线路输出饱和,太小梯度信号微弱。
3.4 训练循环与优化配置:先跑通再谈效果
建议先用一段带噪声的正弦叠加序列做回归任务,序列短、规律明显、loss 下降可预期,适合验证链路是否通。把数据切成固定长度窗口,比如 seq_len=8,预测下一时刻的值。
class QLSTM(nn.Module): def __init__(self, input_size, hidden_size, n_qubits, n_qlayers): super().__init__() self.cell = QLSTMCell(input_size, hidden_size, n_qubits, n_qlayers) self.head = nn.Linear(hidden_size, 1) def forward(self, seq): # seq: [batch, seq_len, input_size] h = torch.zeros(seq.shape[0], self.cell.hidden_size) c = torch.zeros(seq.shape[0], self.cell.hidden_size) for t in range(seq.shape[1]): h, c = self.cell(seq[:, t, :], h, c) return self.head(h) # 用最后时刻的隐藏态预测 model = QLSTM(input_size=1, hidden_size=16, n_qubits=4, n_qlayers=2) optimizer = torch.optim.Adam([ {"params": model.cell.in_proj.parameters(), "lr": 1e-3}, {"params": model.cell.out_proj.parameters(), "lr": 1e-3}, {"params": model.cell.weights, "lr": 5e-2}, # 量子参数单独加大步长 {"params": model.head.parameters(), "lr": 1e-3}, ]) loss_fn = torch.nn.MSELoss() for epoch in range(200): optimizer.zero_grad() pred = model(train_seq) # [batch] loss = loss_fn(pred, train_target) loss.backward() grad_norm = model.cell.weights.grad.norm().item() if epoch % 20 == 0: print(f"epoch {epoch}: loss={loss.item():.4f}, q_grad={grad_norm:.4f}") optimizer.step()逻辑说明:训练走的是 PyTorch 标准流程,但 optimizer 把量子参数 weights 单独分了一组并给了更大学习率。原因是量子线路的梯度天然比经典层小一到两个数量级,混在同一组里会被经典层梯度主导,量子层几乎不动。打印量子参数梯度范数是一个重要诊断手段,如果训练过程中它持续小于 1e-4,说明线路进入平坦区,需要调小层数或换初始化。
参数说明:200 个 epoch 足够这个小任务看到收敛趋势,但别指望 200 步就达到经典 LSTM 精度,混合模型收敛更慢是常态。seq_len 取 8 是为了让反向传播深度适中;更长序列会让量子层每一时间步的开销线性累积,训练时间直接翻倍。
4. 参数设置与调优:让量子 LSTM 稳定收敛的四个关键旋钮
4.1 n_qubits 与输入维度的压缩策略
n_qubits 是最核心的旋钮,同时决定信息瓶颈宽度、线路深度和模拟器内存。常见做法是先按数据复杂度从 4 起步,跑通后再试 6 或 8。n_qubits 每加一,态矢量维度翻倍,default.qubit 的内存和计算量指数增长;在 4 到 6 比特区间,一次前向耗时还在秒级,到 10 比特就要担心 batch 和序列长度了。in_proj 的输出维度必须严格等于 n_qubits。如果这个数比输入特征数小很多,压缩本身就成了前置信息筛选器,有时反而迫使量子层学到更鲁棒的特征,这不是坏事,但要意识到它是有损的。
4.2 n_qlayers 层数与梯度消失的平衡
变分层数控制线路的表达深度。n_qlayers=1 时线路近似一组旋转加纠缠,表达能力有限但梯度信号最强,适合先验证链路;n_qlayers=3 时纠缠更深,理论上能表达更复杂的函数,但参数化量子线路存在贫瘠高原效应——层数越深,损失函数对参数的梯度越接近零,训练越像玄学。我的习惯是先用 2 层跑通,欠拟合再往 3 层走;一旦发现 loss 卡住不动且量子梯度范数掉到 1e-5 以下,先回到 1 层而不是继续加深。
4.3 学习率分组:量子参数为什么需要更大的步长
量子线路的旋转角参数,梯度量级通常只有经典层的十分之一到百分之一,这是由参数化线路的梯度规则决定的,不是实现问题。因此不要单用一个 lr 训整个模型。经典投影层和输出头用 1e-3 到 3e-3,量子权重用 1e-2 到 5e-2。如果量子梯度范数长期大于 1.0,说明 lr 给得太大,旋转角大范围跳动导致线路输出随机化;如果梯度范数小于 1e-4,检查是否初始化太小或层数过深。每次只动一个旋钮,同时改 lr 和层数,翻车了都不知道该怪谁。
4.4 序列长度与 batch 大小对模拟器开销的影响
模拟器开销与三件事成正比:batch、seq_len、线路深度。batch 影响每次量子线路调用要处理的态矢量副本数,PennyLane 的 batch 广播虽然省了 Python 循环,内存占用近似线性增长;seq_len 则是把同一线路反复执行再回传,每个时间步的梯度都要重放整条线路的计算图。实践上建议 batch 先压在 32 以下,seq_len 不超过 32,把单步训练时间控制在秒级。如果任务必须长序列,考虑截断梯度(truncated BPTT)或把量子层只放在部分时间步,后者虽然破坏了严格等价,但能保住训练速度和稳定性。
5. QLSTM 避坑指南:训练翻车的五个典型场景
5.1 现象一:loss 纹丝不动,量子参数梯度接近零
原因:线路堆得太深,或者 weights 初始化过大导致输出饱和,梯度在变分层里逐层衰减到噪声水平;另一个高频原因是 n_qlayers ≥ 4 触发贫瘠高原。解决:先把 n_qlayers 降到 1,weights 初始化为 0.05 倍标准正态,把量子参数 lr 提到 0.05,重跑 50 个 epoch 观察梯度范数是否恢复到 1e-3 量级。恢复了再逐步加深。别在第一次实验里同时调多个旋钮,否则永远分不清是谁治好了。
5.2 现象二:四路门输出全部贴着 0.5,模型退化成均值预测
原因:编码向量 z 经过 sigmoid 后被压缩到 0.5 附近的窄区间,乘以 π 后所有旋转角几乎相同,量子线路对所有输入给出几乎相同的输出;out_proj 的 bias 初始为 0 时 sigmoid(0)=0.5 会加剧。解决:检查 z 的分布,必要时改用 min-max 归一化把数值铺满 (0,1);把 out_proj 的 bias 初始化为 1.0,这是经典 LSTM 常用的遗忘门偏置技巧,让初始模型偏向记住历史信息,而不是输出中性值。
5.3 现象三:加了量子层后训练时间暴涨几个数量级
原因:最常见的是把经典层搬到了 CUDA,而 default.qubit 模拟器在 CPU,每次 forward 都在 CPU 和 GPU 之间同步数据;其次是每时间步每样本调用线路时 Python 循环成了瓶颈。解决:小规模任务干脆全留在 CPU;规模大了再换支持 GPU 的模拟器后端。另外确认 qnode 的 diff_method 用的是 backprop 而不是 parameter-shift,后者采样次数随参数数量线性增长,训练直接慢几十倍。
5.4 现象四:PennyLane 换 Qiskit 后结果对不上
原因:两家框架的实现看似等价,实则三个隐藏差异——编码顺序不同(RY 角度是否在同一层全部施加)、纠缠门排列不同(顺序 CNOT 与环形 CNOT)、测量读数范围不同(PauliZ 期望在 [-1,1],而概率测量在 [0,1])。解决:先统一测量范围,把 PauliZ 期望换成概率读数,公式 (1 + expval) / 2;再把纠缠结构画成线路图对照,确保 CNOT 连接顺序一致;最后用同一组随机输入跑固定权重前向,比对两边门控输出分布,分布重叠了再谈差异。
5.5 现象五:量子层有效但整体不如经典 LSTM
原因:这不是 bug。混合模型的优势在参数效率和表达非线性,不在绝对精度;当任务线性成分占主导,或者 hidden_size 已经大到经典模型轻松拟合时,量子层的信息瓶颈(n_qubits 只有 4 到 6)反而成了拖累。解决:做消融对比——把量子线路换成随机固定矩阵,如果两者精度几乎相同,说明任务不需要量子特征;如果量子版本显著更优,说明瓶颈有效,此时可以试着加大 n_qubits 榨取更多表达空间。
6. 验证量子部分是否真的在工作:三个可靠技巧
6.1 消融:把量子层替换成固定随机矩阵
准备一个对照组,把 quantum_circuit 的权重冻结成随机值,用同样的数据训练,再准备一个经典 LSTM 基线。三组对比的验证集误差如果基本一致,说明量子层只是噪声源;只有量子可训练版本明显优于冻结版本,才说明梯度真的在驱动线路学习有用的表示。这个对照成本很低,却能滤掉大量自我安慰式的结果。
6.2 追踪量子参数:训练前后权重变化与梯度范数
训练前保存 weights 快照,训练结束后计算 L2 距离。距离小于 0.01 量级,说明量子层根本没被训练起来;正常训练的权重位移应该在 0.1 到 1.0。训练过程中每 20 个 epoch 打印一次梯度范数,观察它是否呈先活跃后稳定的形态,也能帮你判断 lr 是否需要下调。我一般把这三行监控代码写进训练脚本,之后任何改动都能立刻判断量子层是否在工作。
6.3 记录模板:三组对比跑同一批数据
用同一份数据切分和同一套指标(训练 loss、验证 RMSE、参数量、单步耗时)记录三组实验,数据放进表格留档。量子部分值不值得做,看的是参数量减半但精度不掉,或者同等参数下验证误差明显更低,而不是 CPU 上的训练速度。
| 模型 | 验证 RMSE | 参数量 | 单步耗时(ms) | 结论 |
|---|---|---|---|---|
| 经典 LSTM | 基线 | 基准 | 基准 | 对照组 |
| QLSTM(权重可训练) | 期望持平或更低 | 目标少一个量级 | 通常更高 | 核心实验 |
| QLSTM(权重冻结) | 应明显更差 | 同左 | 略低 | 消融对照 |
跑这三组实验是我的固定习惯,尤其是冻结权重的对照组,它能一票否决"量子玄学"的错觉。单独看一组实验的数值很容易自我说服,但三组放一起,量子层的真实贡献立刻现形。希望这套从机制到验证的路径能帮你在 qlstm 方向少走弯路。
本文还有配套的精品资源,点击获取