ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

量子LSTM实战:PennyLane TorchLayer混合模型与时序预测拆解

量子LSTM实战:PennyLane TorchLayer混合模型与时序预测拆解

简介:这是一份围绕量子LSTM实现的示例代码包,面向量子机器学习初学者、序列建模研究者以及希望对照经典LSTM复现实验的开发者,重点解决量子循环网络入门与基线比较问题。压缩包共10个文件、约84KB,以3个Python脚本和1个Jupyter Notebook为核心,辅以2个依赖或说明文本、1份Markdown文档及3张训练结果图,整体轻量,便于快速浏览与二次修改。内容涵盖Pennylane、Qiskit等量子计算依赖示例、词性标注任务演示,以及量子LSTM与PyTorch经典LSTM的训练过程对照,Notebook适合交互式阅读,图片可直观呈现训练曲线变化。已有423人学习下载,可作为了解量子LSTM结构、依赖组织与实验对比思路的入门参考,也方便读者在此基础上替换数据集或调整模型参数,开展自己的小规模实验。

1. 从一个跑不出 loss 曲线的 Notebook 说起:qlstm 到底示例了什么

前阵子有人丢过来一个压缩包,说里面的 Notebook 一跑就停在第一个 cell,loss 曲线怎么都不出来。打开一看,是一份 qlstm 示例——他用参数化量子线路替换了经典 LSTM 里的一部分线性变换,整套逻辑装在 Jupyter Notebook 里,逐 cell 演示从编码、构造线路、封装单元到训练评估的完整链路。这类示例的价值不在于刷精度,而在于把「量子线路怎么塞进循环神经网络的门控结构」这件事拆开给你看:量子层负责特征变换,经典层负责门控与状态更新,两边靠一个可微的接口对接。适合已经会写 PyTorch 训练循环、想搞清楚混合量子-经典模型怎么落地的人,也适合拿它当模板改自己时序任务的开发者。下面按我实际跑通和改过的顺序拆。

2. 工程结构与技术选型:Notebook 背后其实是一个五层调用栈

2.1 目录与文件职责:哪些是脚手架,哪些是真东西

这份示例解开之后并不复杂,文件数量不多,但职责切得很清楚。常见的组织方式是这样:

qlstm/ ├── qlstm_demo.ipynb # 主 Notebook,全部演示流程 ├── requirements.txt # 依赖清单 ├── data/ │ └── series.csv # 可选,示例用的时序数据 └── outputs/ ├── loss_curve.png # 训练曲线 └── pred_vs_true.png # 预测对比图

主 Notebook 里通常按 cell 分四段:环境与超参、量子线路定义、模型封装、训练与绘图。requirements.txt是关键,因为量子框架和深度学习框架的版本耦合比较紧,装错版本后面全是玄学报错。data/和outputs/属于可选项,示例里如果没有现成数据文件,Notebook 会用 NumPy 现场合成一段正弦叠加信号,这样别人下载下来不用额外找数据集就能跑。

我一般会做的第一件事,是把 Notebook 里的超参 cell 单独拎出来看:N_QUBITS、N_LAYERS、HIDDEN_DIM、SEQ_LEN、BATCH_SIZE、EPOCHS、LR这几个变量决定了后面所有张量的形状。先把这几个数抄到纸上,再往下读,能省掉大量「这个维度从哪来的」的困惑。

再补一句实际经验:不要在 Notebook 里直接pip install。很多人图省事在第一个 cell 写!pip install pennylane,结果换台机器或者换内核就装到别的环境去了,报错信息还指向一个你根本没装包的 Python。这属于典型的自找麻烦,老老实实用requirements.txt建虚拟环境。

2.2 技术选型:为什么选 PennyLane 的 TorchLayer 而不是手写参数移位

混合量子-经典模型的核心难点是梯度。量子线路里的参数更新需要梯度,而梯度要穿过经典网络回传。常见三条路:

第一条是用 TensorFlow Quantum,功能全但依赖重,TensorFlow 版本一换就容易出问题,Notebook 演示场景下不划算。第二条是手写参数移位规则(parameter-shift rule),自己算线路对每个参数的偏导,能跑但是要把量子线路的参数逐个拆开求导,代码量翻倍,还容易在批量维度上出错。第三条是用 PennyLane 的qml.qnn.TorchLayer,把 QNode 直接包成一个nn.Module,梯度交给 PyTorch 的自动微分处理,在default.qubit这类模拟器上走backprop求导,速度也够。

这份示例走的是第三条。好处很直接:量子层在代码里的存在感和一个nn.Linear差不多,你可以把它塞进任何经典结构里,调试方式、优化器、学习率调度全都复用 PyTorch 那一套。代价是它绑定了模拟器后端,真要上真实量子设备,diff_method得换回parameter-shift,训练速度会掉一个数量级——这一点心里要有数。

选型上还有一个隐含决定:qubit 数量压在 4 个左右。原因是状态向量模拟的显存和计算量随 qubit 数指数涨,4 qubit 的状态向量只有 16 个复数,跑起来毫无压力;上到 12 qubit 以上,Notebook 里一个 epoch 就能让你等得想砸键盘。示例选小规模不是能力限制,是演示节奏的需要。

2.3 环境准备:一次装好并固定随机种子

依赖装法:

python -m venv .venv source .venv/bin/activate # Windows 用 .venv\Scripts\activate pip install --upgrade pip pip install pennylane torch numpy matplotlib

这四五个包就够了。torch装 CPU 版即可,量子模拟器本身也不吃 GPU。装完之后在 Notebook 第一个 cell 里固定种子:

import random import numpy as np import torch import pennylane as qml SEED = 42 random.seed(SEED) np.random.seed(SEED) torch.manual_seed(SEED) # 量子线路参数初始化也走 torch 的随机源,保持一致 print("pennylane:", qml.__version__) print("torch:", torch.__version__)

固定种子的意义在于:量子线路的初始参数是随机采样的,不固定的话每次重跑 loss 起点都不一样,你没法判断某次改动是真有效还是碰巧。我在 Notebook 里习惯把种子设置和版本打印放在同一个 cell,这样别人复现出问题时,截图里就带着版本信息,省一轮来回问。

qml.__version__那行别省。不同大版本的TorchLayer在权重形状约定上改过,报错信息又很隐晦,先把版本亮出来,排查时能少走弯路。

3. 量子 LSTM 单元拆解:一个时间步里数据怎么流

3.1 角度编码:把拼接后的特征压到 4 个量子比特

经典 LSTM 的第一步是把当前输入x_t和上一时刻隐状态h_{t-1}拼起来,做一次线性变换得到四个门的前激活值。这份示例在这一步插入了量子线路:先用一个经典线性层把拼接向量降到N_QUBITS维,再用角度编码把这几个数映射成量子比特的旋转角。

import torch import torch.nn as nn import pennylane as qml N_QUBITS = 4 N_LAYERS = 2 # 状态向量模拟器,4 个 qubit 只有 16 个复数,跑起来很轻 dev = qml.device("default.qubit", wires=N_QUBITS) @qml.qnode(dev, interface="torch", diff_method="backprop") def quantum_circuit(inputs, weights): # 角度编码:每个特征值映射成一个 Y 轴旋转角 qml.AngleEmbedding(inputs, wires=range(N_QUBITS), rotation="Y") # 变分线路:带纠缠的强纠缠层 qml.StronglyEntanglingLayers(weights, wires=range(N_QUBITS)) # 逐比特测量 Z 方向期望值,输出范围 [-1, 1] return [qml.expval(qml.PauliZ(i)) for i in range(N_QUBITS)]

这里几个参数要讲清楚。wires=N_QUBITS决定线路宽度,也就是能编码多少个特征;rotation="Y"表示用 Ry 门做编码,输入值直接当旋转角用。角度编码有个硬约束:输入必须落在合理区间,否则角度会绕圈,不同的输入可能映射到同一个量子态上,特征就撞车了。所以pre那个线性层后面通常接一个tanh再乘π/2,把值压到(-π/2, π/2)。

interface="torch"告诉 PennyLane 这个 QNode 的输入输出都是 torch 张量;diff_method="backprop"表示用反向传播求导,这是状态向量模拟器上最快的选择。换成真实设备或某些采样后端时,这一项必须改成"parameter-shift",否则直接报错。

3.2 变分线路与测量:ansatz 的参数量是怎么算出来的

StronglyEntanglingLayers是 PennyLane 内置的一个常用 ansatz,结构是「旋转层 + 纠缠层」交替堆叠。它的权重形状是固定的三元组:

# 形状约定:(层数, 比特数, 3) # 每层每个比特有 3 个旋转角(Rot 门),层间用 CNOT 环形连接 weight_shape = {"weights": (N_LAYERS, N_QUBITS, 3)} print(weight_shape) # {'weights': (2, 4, 3)}

参数量就是2 × 4 × 3 = 24个。这个数字很关键:它决定了量子部分的可训练容量。N_LAYERS加大,表达能力变强,但训练也更难收敛;N_LAYERS=1时纠缠只发生一次,很多时候退化成近似线性的变换,量子带来的非线性优势体现不出来。

测量部分返回N_QUBITS个 PauliZ 期望值,每个值理论上落在[-1, 1]。这一点直接影响后面门控的设计:期望值天然有界,不需要额外加激活函数去截断,但也意味着信息被压在一个较小的动态范围里,如果四个门都用同一批期望值线性变换出来,容易出现「门开合幅度不够」的问题。我的处理办法是给每个门单独一个线性头,让不同门从同一组期望值里抽取不同的线性组合。

顺带说一个容易忽略的点:StronglyEntanglingLayers要求比特数至少为 2,而且当比特数是奇数时,最后一层的纠缠模式会和偶数时不同。示例选 4 是安全值,你改成 3 或 5 的时候别指望行为完全一致,得重新调。

3.3 门控融合:量子层输出接回输入门、遗忘门、候选态和输出门

真正把量子层和 LSTM 缝在一起的是下面这个单元类。思路是:量子层输出N_QUBITS维向量,用一个线性层升到4 × hidden_dim,然后按 LSTM 的标准切法拆成四个门。

class QuantumLSTMCell(nn.Module): def __init__(self, input_dim, hidden_dim, n_qubits=4, n_layers=2): super().__init__() self.hidden_dim = hidden_dim # 经典降维:把 x_t 和 h_{t-1} 拼起来压到量子比特数 self.pre = nn.Linear(input_dim + hidden_dim, n_qubits) # 量子层,权重形状必须和 ansatz 约定一致 self.qlayer = qml.qnn.TorchLayer( quantum_circuit, {"weights": (n_layers, n_qubits, 3)} ) # 经典升维:量子测量结果 -> 四个门 self.post = nn.Linear(n_qubits, 4 * hidden_dim) def forward(self, x, state): h_prev, c_prev = state # 拼接 + 压缩 + 限幅到角度编码的安全区间 z = torch.cat([x, h_prev], dim=-1) z = torch.tanh(self.pre(z)) * (torch.pi / 2) # 过量子线路,得到 n_qubits 个期望值 q_out = self.qlayer(z) # 升维并切成 i / f / g / o 四个门 gates = self.post(q_out) i, f, g, o = gates.chunk(4, dim=-1) i = torch.sigmoid(i) # 输入门 f = torch.sigmoid(f) # 遗忘门 o = torch.sigmoid(o) # 输出门 g = torch.tanh(g) # 候选态 c_new = f * c_prev + i * g h_new = o * torch.tanh(c_new) return h_new, (h_new, c_new)

逐段说明。self.pre的输出维度是n_qubits而不是hidden_dim,这是整个设计里最容易改错的地方——很多人下意识把它写成hidden_dim,然后在qlayer那里拿到形状不匹配的报错。torch.tanh(...) * (torch.pi / 2)这一行是角度编码的限幅,把值约束在(-π/2, π/2),避免角度绕圈。

qml.qnn.TorchLayer的第二个参数是权重形状字典,键名必须和 QNode 的形参名weights对上。这里如果写成(n_qubits, n_layers, 3),运行时会报维度错误,而且报错信息不会告诉你是顺序反了,得自己对着文档核。

gates.chunk(4, dim=-1)按最后一维均分四份,每份hidden_dim。注意 chunk 的顺序就是 i、f、g、o,和经典 LSTM 的约定一致,不要自己调换,否则模型还是能训,但语义完全变了,调试时会怀疑人生。

self.post是一个纯经典线性层。有人会问:既然量子层都上了,为什么还要经典层把它升回去?因为量子测量只给了n_qubits维信息,而门控需要4 × hidden_dim维,中间的映射必须由经典层承担。这也说明一个事实:这份示例里量子部分承担的是特征变换,不是参数量的替代品,别指望用量子层减少可训练参数。

3.4 时序封装与形状核算:batch、seq_len、hidden 三个维度的对齐

单元写好了,还得在外面套一层按时间步展开的循环。这部分没有任何量子成分,就是标准的 PyTorch 写法:

class QuantumLSTM(nn.Module): def __init__(self, input_dim, hidden_dim, n_qubits=4, n_layers=2): super().__init__() self.hidden_dim = hidden_dim self.cell = QuantumLSTMCell(input_dim, hidden_dim, n_qubits, n_layers) self.out = nn.Linear(hidden_dim, 1) # 回归到单值预测 def forward(self, x): # x: (batch, seq_len, input_dim) batch, seq_len, _ = x.shape h = torch.zeros(batch, self.hidden_dim, dtype=x.dtype) c = torch.zeros(batch, self.hidden_dim, dtype=x.dtype) for t in range(seq_len): h, (h, c) = self.cell(x[:, t, :], (h, c)) return self.out(h).squeeze(-1) # (batch,)

形状链条是这样走的:输入(batch, seq_len, input_dim),切时间步后每个x[:, t, :]是(batch, input_dim);pre把它和(batch, hidden_dim)的h拼成(batch, input_dim + hidden_dim),降到(batch, n_qubits);量子层逐样本处理,输出还是(batch, n_qubits);post升到(batch, 4 * hidden_dim);切完门控,c_new和h_new都是(batch, hidden_dim)。

有两个细节值得强调。第一,torch.zeros那里显式写了dtype=x.dtype,因为TorchLayer内部对精度敏感,如果输入是float32而初始状态是float64,拼接时就会报 dtype 不一致,这个错在 Notebook 里出现频率极高。第二,时间步循环是 Python 的for,没有并行化,seq_len一大训练就慢。示例里seq_len一般设在 20 到 50 之间,够演示又不至于等太久。

4. 训练与验证:在 Notebook 里把 loss 曲线跑下来

4.1 造一份可控的时序数据:先让经典模型能学会

示例不依赖外部数据集,用合成信号更可控。我的做法是叠两个不同频率的正弦再加一点噪声,用滑窗切样本:

import numpy as np import torch def make_series(n=1200, noise=0.05, seed=0): rng = np.random.default_rng(seed) t = np.arange(n) # 两个频率叠加,保证模型必须记住一定历史才能预测准 s = np.sin(2 * np.pi * t / 40) + 0.5 * np.sin(2 * np.pi * t / 13) s = s + rng.normal(0, noise, size=n) return s.astype(np.float32) def make_windows(series, seq_len=32): xs, ys = [], [] for i in range(len(series) - seq_len): xs.append(series[i:i + seq_len]) ys.append(series[i + seq_len]) x = torch.from_numpy(np.stack(xs)).unsqueeze(-1) # (N, seq_len, 1) y = torch.from_numpy(np.stack(ys)) # (N,) return x, y series = make_series() x, y = make_windows(series, seq_len=32) split = int(0.8 * len(x)) x_train, y_train = x[:split], y[:split] x_val, y_val = x[split:], y[split:] print(x_train.shape, y_train.shape)

seq_len=32意味着每个样本看 32 个历史点,预测第 33 个。input_dim=1,因为序列是单变量。这段数据的周期是 40 和 13,模型必须同时抓住这两个尺度才能把误差压低,正好用来检验隐状态是否真的记住了历史。

关键的一步是先跑一个纯经典 LSTM 做基线。如果经典模型都学不动,说明数据切法或者学习率有问题,这时候去调量子部分纯属浪费。我在 Notebook 里会保留一个ClassicLSTM的 cell,训练两三个 epoch 看 loss 能不能降到 0.1 以下,能降再换量子版。

4.2 训练循环:梯度怎么穿过量子线路回来

训练循环和普通 PyTorch 没区别,这是选TorchLayer带来的最大便利:

import torch.nn as nn from torch.optim import Adam model = QuantumLSTM(input_dim=1, hidden_dim=16, n_qubits=4, n_layers=2) opt = Adam(model.parameters(), lr=5e-3) loss_fn = nn.MSELoss() train_losses, val_losses = [], [] for epoch in range(30): model.train() opt.zero_grad() pred = model(x_train) loss = loss_fn(pred, y_train) loss.backward() # 梯度穿过量子层回传到 pre / weights / post opt.step() train_losses.append(loss.item()) model.eval() with torch.no_grad(): val_loss = loss_fn(model(x_val), y_val).item() val_losses.append(val_loss) if epoch % 5 == 0: print(f"epoch {epoch:3d} | train {loss.item():.4f} | val {val_loss:.4f}")

loss.backward()这一行是整个示例的技术核心。它之所以能穿过量子线路,是因为diff_method="backprop"让 QNode 在状态向量模拟器上以普通可微计算图的形式存在,PyTorch 的 autograd 可以直接对它求导。如果后端换成采样设备,这一行会抛异常,提示你改求导方式。

学习率我一般从5e-3起,量子层的参数初始值在[-0.1, 0.1]附近,梯度本身不大,学习率太小学不动,太大又会在初期震荡。hidden_dim=16是个折中:太小记不住 32 步的历史,太大经典层参数量上来,量子层那点表达能力又被淹没,看不出量子到底起了什么作用。

训练轮数不用多。这类合成数据 20 到 30 个 epoch 就能看出趋势,如果 loss 一直卡在某个值不动,八成不是轮数不够,而是学习率、限幅或者量子层宽度出了问题,继续加 epoch 只是浪费时间。

4.3 结果读取与 Notebook 执行顺序

训练完之后画两条曲线,训练集和验证集放一张图:

import matplotlib.pyplot as plt plt.figure(figsize=(7, 4)) plt.plot(train_losses, label="train") plt.plot(val_losses, label="val") plt.xlabel("epoch") plt.ylabel("MSE") plt.legend() plt.tight_layout() plt.savefig("outputs/loss_curve.png", dpi=150)

判断标准很简单:验证 loss 跟着训练 loss 一起往下走,说明泛化正常;验证 loss 早早翘头,说明hidden_dim太大或者seq_len太长,模型在背数据。这份示例的定位是演示,σ 到 0.05 量级的噪声下,验证 MSE 能压到 0.02 以内就算跑通了。

Notebook 的执行顺序是另一个隐形成本。Jupyter 允许你乱序执行 cell,定义模型的 cell 跑两次,第二次会把model变量重新绑定到一个新实例上,但优化器可能还指着旧参数,训练时看起来在跑,实际什么都没更新。我的血泪经验是:改完任何 cell,先Kernel → Restart & Run All,宁可多等三十秒,也不要在一堆残留状态上做判断。图表和图例里出现的 loss 数值,只有在顺序执行的前提下才有意义。

5. 避坑与排查:五处最容易翻车的地方

5.1 报 dtype 不一致:float32 与 float64 打架

现象:跑到torch.cat([x, h_prev], dim=-1)或者qlayer输入那一步,报错信息大意是两个张量 dtype 不匹配,一个是Float一个是Double。

原因:TorchLayer内部默认按float64构造权重,而torch.zeros不给dtype时默认float32,或者反过来,数据用 NumPy 生成的是float64,模型权重是float32。量子框架对精度比纯经典模型敏感,混着用就炸。

解决:统一在入口处做一次转换。数据侧torch.from_numpy(arr).float(),初始状态torch.zeros(batch, hidden, dtype=x.dtype),模型整体.to(torch.float32)。我习惯在数据生成函数里就.astype(np.float32),从源头掐断。

5.2 权重形状写反,报错指向一个不相干的行

现象:构造TorchLayer之后第一次前向就报维度错误,报错位置在qml.StronglyEntanglingLayers内部,看起来像是 ansatz 的问题。

原因:权重形状字典写成了(n_qubits, n_layers, 3)或者(n_layers, 3, n_qubits)。StronglyEntanglingLayers只认(层数, 比特数, 3),顺序反了不会提前提示,而是在展开旋转门的时候才暴露。

解决:把形状单独打印出来核对一遍,print({"weights": (N_LAYERS, N_QUBITS, 3)}),确认第一个维度是层数。如果你改过N_QUBITS或N_LAYERS,这两处要同步改,别只改一处。

5.3 Notebook 重复执行,模型参数被静默重置

现象:loss 曲线每次跑出来的起点都不一样,明明固定了种子;或者训练了三十轮,参数看起来根本没变。

原因:定义模型和优化器的 cell 被重复执行过。第二次执行model = QuantumLSTM(...)会创建一个全新实例,而opt如果也重新建了倒是没事,怕的是只重跑了模型 cell 没重跑优化器 cell,优化器手里攥着旧参数,opt.step()更新的是一批已经不在计算图里的张量。

解决:每次改动后 Restart & Run All。想在 Notebook 里做增量实验,就把「建模 + 优化器 + 训练」打包成一个函数,调用一次生成一套全新状态,避免依赖 cell 的执行历史。

5.4 换成真实后端后训练直接崩

现象:把default.qubit换成别的后端,loss.backward()抛异常,提示不支持反向传播。

原因:diff_method="backprop"只在状态向量模拟器上可用。采样型后端拿不到解析梯度,必须走参数移位规则。

解决:改@qml.qnode(dev, interface="torch", diff_method="parameter-shift"),同时在训练循环里把 batch 调小,因为参数移位对每个参数要做两次额外线路评估,耗时随参数量线性增长。4 qubit 24 个参数还能忍,参数一多就得考虑分组或者换回模拟器。

5.5 loss 卡住不动,不是学习率的问题

现象:训练几十轮,loss 一直在 0.3 附近小幅抖动,看不出下降趋势。

原因:两个可能。一是pre层后面没做角度限幅,输入值超出(-π/2, π/2),编码后量子态高度重合,量子层输出几乎是常数,梯度接近零。二是N_LAYERS=1且N_QUBITS太小,线路表达能力不足以区分不同输入。

解决:先检查限幅那行torch.tanh(self.pre(z)) * (torch.pi / 2)有没有被误删。确认没问题后,把N_LAYERS从 1 加到 2,观察 loss 是否开始下降。还不行就临时把量子层换成nn.Linear(n_qubits, n_qubits)做个对照实验,如果经典替换版能训而量子版不能,问题就锁定在线路本身。

6. 二次开发:三种改法与一套验证习惯

想在这份示例上做二次开发,改动点其实集中在三个地方,风险各不相同。

改造点改哪里影响风险
换编码方式quantum_circuit里的AngleEmbedding特征表达能力振幅编码需要2^n维输入,pre层要跟着改
换 ansatzStronglyEntanglingLayers换成自定义层参数量与纠缠模式权重形状字典必须同步重算
改门控结构QuantumLSTMCell.forward的门计算模型语义改完等于换了个模型,旧超参全部失效

换编码是最容易上手的。把角度编码换成振幅编码,代码只需要几行:

@qml.qnode(dev, interface="torch", diff_method="backprop") def quantum_circuit_amp(inputs, weights): # 振幅编码要求输入是 2^n 维且模长为 1,这里用 L2 归一化兜底 norm = torch.sqrt(torch.sum(inputs ** 2)) + 1e-8 qml.AmplitudeEmbedding(inputs / norm, wires=range(N_QUBITS), normalize=True) qml.StronglyEntanglingLayers(weights, wires=range(N_QUBITS)) return [qml.expval(qml.PauliZ(i)) for i in range(N_QUBITS)]

注意pre层的输出维度得从n_qubits改为2 ** n_qubits,4 qubit 就是 16 维。编码维度一变,pre的参数量翻了四倍,训练时间也跟着涨,这一层联动关系在改之前就要想清楚。

门控结构的改法更激进一些。经典 LSTM 的遗忘门是sigmoid(线性变换),有人会把它换成量子态的保真度或者某个测量概率,这样门的开合来自量子线路本身而不是经典层。这么改之后模型语义变了,之前调好的学习率、hidden_dim基本都要重调。我的建议是先在纸上把新的门公式写出来,确认梯度路径还是通的,再动代码。

验证习惯这块,我踩过的坑值得说一下。第一次改 ansatz 的时候,我把层数加到 4,训练 loss 确实降得比原来快,当时以为找到了甜点,结果换了一份数据完全不复现。后来才想明白:那次对比没有固定种子,也没有跑经典基线。从那以后我每次改量子部分,都强制走一遍三步——固定种子重启内核全量执行、同参数跑一次经典 LSTM 基线、同一份数据上跑三次取均值。多花十分钟,省掉的是整整一周的自欺欺人。

量子模型的效果波动本来就比经典模型大,参数初始化、线路结构、编码方式任何一处动了,结果都可能天差地别。别指望一次实验就下结论,也别因为某次 loss 好看就急着写总结。希望这份拆解能帮你把 Notebook 顺利跑起来,少走几步弯路。

本文还有配套的精品资源,点击获取

返回列表