☰
量子K-means文本聚类实战:从swap test原理到代码实现
2026/10/2 20:13:47 网站建设 项目流程

简介:面向量子机器学习与聚类分析研究者的代码与笔记合集,围绕量子K-means(QK-means)算法展开,针对传统K-means在大规模高维数据上效率下降的问题,给出基于量子线路加速聚类流程的改进实现。压缩包共20个文件、约1.06MB,以14个Jupyter Notebook为核心,从量子比特表示、距离计算与控制门线路,到QK-means自动化输出和MTEB文本嵌入测试均有覆盖;另含JSON/CSV数据、README、说明txt与附赠Word笔记,便于对照实验与复现。资源还整理了经典S2S、PicturetoP等对比实验,以及Hugging Face、NumPy保存数据等配套脚本;结合自建数据集构建和V-measure评估,可完整走通数据构造、特征表示、聚类训练到效果量化的流程,并对量子比特退相干、误差控制等实现难点给出测试与排错思路。目前已有81人学习/下载。适合想理解量子聚类代码实现、开展文本嵌入实验的研究者,也适合作为量子机器学习入门后的实战参考。

1. QK-means 到底改了 K-means 的哪一步:先分清混合计算和全量子

很多人第一次看到“量子K-means”这个组合,第一反应是“把 K-means 搬到量子计算机上跑”。真实落地时并不是这样。经典 K-means 每一轮迭代最大的开销是计算 n 个样本到 k 个质心的欧氏距离,维度 d 越高,这一步越贵。QK-means 的核心思路是用量子态之间的内积估计来替换逐维浮点运算,用振幅编码把 d 维向量装进 log₂d 个量子比特里,再用 swap test 一次读出相似度。于是原来复杂度 O(nkd) 的距离矩阵计算,在原理上被压到了 O(nk log d) 的线路深度加常数次测量。这个标题里的“应用实现”,就是把这条混合路线在文本聚类场景里跑通:经典部分负责嵌入、质心更新和评估,量子部分只干距离计算这一件事。

这套方案不值得在模拟器上追求性能收益,它的价值在于验证算法路径和评估口径。适合三类人:研究量子算法落地的从业者,做文本聚类但想探索下一代计算范式的工程师,以及需要在自建数据集上给 QK-means 打分写结论的人。

2. 从欧氏距离到量子态内积:QK-means 的核心原理与选型理由

2.1 经典 K-means 的瓶颈:高维文本向量上的距离计算

K-means 的迭代逻辑很简洁:分配阶段把每个样本归到最近的质心,更新阶段把每个簇的均值作为新质心。但如果把文本嵌入向量直接喂进去,事情就不那么优雅了。用 sentence-transformers 这类模型产出的向量,常见维度是 384、768 甚至 1024,一次完整迭代要算 n×k 个 d 维欧氏距离,每个距离做 d 次乘加。n 到万级、k 到几十时,距离矩阵成了整个算法的绝对瓶颈。

经典优化不是没有:KD-Tree 和三角形不等式剪枝在低维数据上效果显著,但在文本嵌入这种高维且分布稠密的场景里基本失效。这是维度灾难的典型表现——高维空间中距离趋于均匀,树结构的剪枝收益被迅速稀释。Mini-Batch K-means 是工程上最常用的妥协方案,但它改的是采样方式,不是距离计算本身。QK-means 选择直接攻击最贵的那块:用量子线路估计向量间的相似度,再把相似度换算成距离。这不是替代 K-means 的全部,而是精准替换其内层最热路径。

2.2 振幅编码为什么只花 log₂d 个量子比特:swap test 线路推导

要把经典向量送进量子线路,最常见的手段是振幅编码。一个 d 维单位向量 x,被直接写进 2ⁿ 个基态的概率幅上,其中 n = ⌈log₂d⌉。也就是说,384 维的文本嵌入只需要 9 个量子比特就能装下,768 维只需要 10 个。对比经典内存里存 384 个浮点数,这个压缩比是量子表示带来的第一层红利。代价是概率幅本身看不见摸不着,只能通过测量间接读取,而且读取的是概率而不是确定值。

距离计算部分用的是 swap test。线路结构是:一个辅助比特先过 Hadamard 门,两份待比较的量子态分别放在两个寄存器里,然后以辅助比特为控制位,对两个寄存器逐比特做控制交换(cswap),最后再对辅助比特做 Hadamard 门并测量。测量结果为 |0⟩ 的概率满足 P(0) = (1 + |⟨ψ|φ⟩|²) / 2。这个式子很好用:把它整理一下,|⟨ψ|φ⟩|² = 2P(0) − 1,直接得到两个单位向量内积平方的估计值,也就是量子保真度。

对于单位向量,欧氏距离和保真度之间存在一个非常干净的换算关系:‖a − b‖² = 2 − 2⟨a|b⟩。所以只要把 swap test 估计出的内积平方当作 cos²θ,距离平方就等于 2(1 − cos²θ) 的补数关系吗?这里要小心。swap test 给出的是内积绝对值平方,而距离公式需要带符号的内积。如果直接用 |⟨a|b⟩|² 代进 2 − 2⟨a|b⟩,需要先判断符号。文本嵌入向量经过大多数模型的激活函数输出后通常是非负的,内积天然非负,符号问题被绕开了。但换到其他领域,必须先验证这个前提,否则距离矩阵会错得悄无声息。这一点在第 5 章还会展开。

2.3 为什么当前落地选 swap test 而非 Grover 优化

量子 K-means 还有另一条常见路线:用 Grover 算法加速“寻找最近质心”这一步,把每轮分配的搜索开销从 O(k) 降到 O(√k)。听起来更诱人,但工程落地有两个现实障碍。第一,Grover 需要针对每个样本构造一个 oracle,用来标记“哪些质心比当前候选更近”,这个 oracle 的构造本身就是一次距离比较,复杂度并不低;第二,Grover 对量子内存的要求更高,当前 NISQ 阶段很难在真实硬件上稳定跑出有意义的加速。

swap test 路线的优势在于浅和稳。线路深度只随 log₂d 增长,辅助比特只需要一个,测量结果直接换算成距离值,不需要复杂的振幅放大流程。所以在“应用实现”这个标题的语境下,swap test 几乎是唯一成熟、可复现、能在模拟器上跑完整个 K-means 迭代的选择。模拟器上它当然不会比经典 NumPy 更快,但算法路径是完整的:距离来自量子线路,聚类质量可以量化评估。先把这条路走通,等硬件成熟后才有参数和基线可以对照。

3. 用 QK-means 跑通文本聚类:嵌入、线路与迭代代码

3.1 工具链选型与项目结构

我一般会把这类项目拆成三个模块:嵌入与数据准备、量子距离估计、聚类主循环与评估。量子部分用 Qiskit 的 AerSimulator 做状态向量模拟,经典部分用 sentence-transformers 产出文本嵌入,scikit-learn 提供 V-measure 等评估指标。三个模块各干各的事,换库成本最低。

模块职责关键依赖
embedding.py文本转向量,输出归一化嵌入sentence-transformers, numpy
quantum_distance.py用 swap test 估计向量间保真度qiskit, qiskit-aer
qkmeans.py聚类迭代、质心更新、评估numpy, scikit-learn

选择这个组合的考虑是:Qiskit 的 AerSimulator 对中小规模线路足够稳定,initialize 方法可以直接把经典向量编码进量子态,省去手动构造态制备线路的工作。sentence-transformers 负责把原始文本变成干净的数值输入,这样量子部分专注做距离估计,聚类逻辑完全透明。

3.2 文本嵌入与自建数据集准备

文本嵌入阶段最容易被忽视的是归一化。swap test 推导全程假设输入是单位向量,没有归一化的向量直接送进 initialize,内积估计的物理意义就会漂移。我通常在 encode 时直接打开 normalize_embeddings 参数,一步到位。

from sentence_transformers import SentenceTransformer import numpy as np # 自建小型聚类数据集:结构上模仿 MTEB 聚类任务的“文本列表 + 标签列表”二元组 corpus = [ "量子纠错码的阈值定理决定了容错计算的可行性", "表面码通过稳定子测量实现逻辑量子比特的冗余保护", "K-means 的初始质心选择会显著影响最终聚类结果", "DBSCAN 通过密度连接识别任意形状的数据簇", "梯度裁剪可以缓解深度 Transformer 训练中的梯度爆炸", "混合精度训练在保持训练精度的同时降低显存占用", ] labels = [0, 0, 1, 1, 2, 2] model = SentenceTransformer("paraphrase-multilingual-MiniLM-L12-v2") vecs = model.encode(corpus, normalize_embeddings=True) print(vecs.shape) # (6, 384)

这段代码的输出是 6 条文本对应的 384 维单位向量。normalize_embeddings=True 是关键参数,它保证每条向量的 L2 范数为 1,让 swap test 的距离换算公式成立。数据规模上,自建数据集每类建议至少 20 条,类别数控制在 3 到 5 个,否则 V-measure 的区分度不够,量子估计误差和聚类随机性会混在一起。

3.3 量子距离估计核心代码:swap test 的实现与换算

量子距离估计是整个项目的核心模块。逻辑分三步:把两个向量分别补零对齐到 2ⁿ 维,用 initialize 编码进两个寄存器,用 swap test 读出保真度。

from qiskit import QuantumCircuit from qiskit_aer import AerSimulator import numpy as np def swap_test_fidelity(vec_a, vec_b, shots=8192): """估计两个单位向量之间的保真度 |<a|b>|^2。 注意:vec_a 和 vec_b 必须是单位向量。 """ dim = vec_a.shape[0] n = max(1, int(np.ceil(np.log2(dim)))) # 不足 2^n 的维度补零,让两个向量拥有相同的量子比特表示 padded_a = np.zeros(2 ** n) padded_b = np.zeros(2 ** n) padded_a[:dim] = vec_a padded_b[:dim] = vec_b padded_a /= np.linalg.norm(padded_a) padded_b /= np.linalg.norm(padded_b) qc = QuantumCircuit(2 * n + 1, 1) qc.h(0) # 辅助比特进入叠加态 qc.initialize(padded_a, range(1, n + 1)) # 第一个寄存器编码向量 a qc.initialize(padded_b, range(n + 1, 2 * n + 1)) # 第二个寄存器编码向量 b for i in range(n): qc.cswap(0, 1 + i, 1 + n + i) # 控制交换两组寄存器 qc.h(0) # 辅助比特再次 Hadamard qc.measure(0, 0) counts = AerSimulator().run(qc, shots=shots).result().get_counts() p0 = counts.get("0", 0) / shots fidelity = max(0.0, 2 * p0 - 1.0) # 由 P(0) 反解内积平方 return fidelity

这里的 n 由输入维度动态推导。384 维向量补零到 512 维,需要 9 个量子比特,两个寄存器加一个辅助比特共 19 个量子比特,AerSimulator 完全能承受。cswap 是控制交换门,对应 swap test 线路中的核心操作。fidelity 换算公式 2p₀ − 1 直接来自 P(0) = (1 + |⟨ψ|φ⟩|²) / 2,max 截断是为了处理有限采样造成的负值。如果想把距离直接用上,单位向量下的欧氏距离平方就是 2(1 − fidelity)。

3.4 跑起来的参数:量子比特数、shots 与特征维度怎么定

量子比特数和维度是直接挂钩的:总量子比特数 = 2 × ⌈log₂d⌉ + 1。下面是常见维度的资源配置参考表。

嵌入维度 d振幅编码量子比特swap test 总量子比特shots 建议
8(PCA 降维后)372048
325114096
1287158192
3849198192
768102116384

shot 数量直接决定保真度估计的方差。理论上 P(0) 的估计方差正比于 P(0)(1−P(0))/shots,当真实保真度接近 0 或 1 时方差会自然变小,中间区域需要更多采样。从 8192 起步,如果发现同一对向量的重复估计波动超过 0.05,就把 shots 翻倍。这个现象在代码里很好排查,把同一个样本对放进 swap_test_fidelity 跑三遍看输出即可。

维度选择有一个实践技巧:先用 PCA 把 384 维降到 8 维或 32 维跑通整个流程,确认聚类迭代和评估代码没有 bug,再用原始维度做精度验证。降维会损失语义信息,但流程验证阶段追求的是快和稳,不是分数。我一般会保留两套配置,一套给 CI 回归用,一套给最终实验用。

4. 借 MTEB 基准测试的口径,在自建数据集上评估聚类质量

4.1 为什么不自接跑完整 MTEB 基准测试

MTEB(Massive Text Embedding Benchmark)是一个覆盖面很广的文本嵌入评测基准,把嵌入模型丢进包括聚类、检索、分类、语义相似度在内的多个任务里统一打分。它的聚类任务基本协议是:给一组带话题标签的句子或段落,嵌入后跑聚类,再用 V-measure 和 ARI 这类指标汇报聚类质量。这个协议本身非常清晰,但完整复刻整套 MTEB 的成本很高,数据集规模和任务数量都不是为单篇技术验证设计的。

我们在这里真正要评估的不是嵌入模型,而是 QK-means 这个聚类算法。评估目标不同,就不该背上整个基准的包袱。我一般只沿用 MTEB 聚类任务的口径:文本加真实标签,嵌入后聚类,聚类结果与真实标签计算 V-measure。这样既保证了评估方法有据可依,又让实验可以在笔记本上几分钟内完成。

4.2 自建数据集的三种构建方式

自建数据集的构建方式决定了评估结论的适用范围,我常用三种。第一种是公开语料采样,找带类别标签的新闻、论文摘要或商品评论,每个类别随机抽几十条。这种方式标签可靠、覆盖真实分布,适合最终验证。第二种是从自己的业务文档里标注,适合验证 QK-means 在特定领域文本上的表现,代价是标注成本高。第三种是模板合成数据,用少量种子文本加上同义改写或模板组合生成语料,适合先跑通全流程。

三种方式在评估价值上有明显差异。公开语料采样的结论可以外推到同类文本,业务标注的结论只对本领域有效但最贴近生产,合成数据只能用来验证代码正确性。我通常的做法是:开发阶段用合成数据,参数调优阶段用业务标注数据,写结论前用公开语料做一次干净的对比实验。这个顺序能最大程度避免把 bug 当成实验结果。

4.3 V-measure 评估与基线对比

V-measure 是聚类结果与真实标签之间的一致性度量,由同质性(homogeneity)和完整性(completeness)的调和平均得到。它不要求两边类别编号对齐,只关注信息一致性,适合评估这种自建数据集上的聚类算法。

from sklearn.metrics import v_measure_score, homogeneity_score, completeness_score y_true = np.array(labels) y_pred = qkmeans(vecs, k=3) # 见 3.3 节的主循环 v = v_measure_score(y_true, y_pred) h = homogeneity_score(y_true, y_pred) c = completeness_score(y_true, y_pred) print(f"V-measure={v:.3f} homogeneity={h:.3f} completeness={c:.3f}")

这段代码的唯一依赖是 scikit-learn,评估指标本身不关心聚类是量子算出来的还是经典算出来的。跑对比实验时,我会在同一份嵌入上并行跑经典 K-means、scikit-learn 的 K-Means 和 QK-means,三者输出 V-measure 的平均值和标准差。量子版本如果和经典版本差距在 0.05 以内,说明量子距离估计没有破坏聚类结构;差距过大,优先怀疑距离换算公式或归一化问题,而不是量子硬件噪声。

对比实验设计有一个细节:经典 K-means 和 QK-means 必须使用相同的初始质心和相同的迭代上限,否则差异会混杂初始化随机性。固定随机种子是最低要求,更稳妥的做法是多次初始化取平均分。小数据集上单次运行的 V-measure 方差可能达到 0.1,不看均值只看单次结果很容易得出错误结论。

5. 量子K-means 落地避坑:模拟器上的四个常见误判

5.1 距离估计波动大,聚类结果时好时坏

现象:同一份数据和固定种子,QK-means 跑两次,V-measure 一次 0.82 一次 0.61,差距大到无法判断算法是否有效。

原因:swap test 本质是概率测量,有限 shots 下保真度估计存在采样误差。当真实内积处在 0.5 附近时,P(0) 的斜率最大,换算成保真度后误差被放大。聚类迭代过程中一个小误差可能改变样本分配,进而改变质心,误差在迭代中累积放大。

解决:先把 shots 提到 16384 以上,然后对每个样本对重复估计三次,取中位数作为距离值。中位数比均值对离群噪声更鲁棒。如果波动仍然明显,检查一下是否所有向量都做了归一化——未归一化向量的内积平方可能超过 1,直接破坏整个距离矩阵。

5.2 忘记归一化,距离公式全盘错乱

现象:V-measure 分数和经典 K-means 差出 0.3 以上,甚至不如随机标签。

原因:swap test 推导链路上每一步都要求单位向量。距离换算公式 d² = 2(1−fidelity) 只对单位向量成立。如果嵌入向量模长不均匀,内积平方丢失了模长信息,欧氏距离的模长项完全没有进入计算。文本嵌入场景下,不同文本的向量模长差异往往携带语义显著度信息,丢掉它聚类结构必然受影响。

解决:在 sentence-transformers 的 encode 调用里直接打开 normalize_embeddings=True,或者在送入 swap_test_fidelity 前手动除以 L2 范数。我还会顺手打印几个样本的范数做断言,如果范数偏离 1 超过 1e-6,立即中断并提示归一化错误。

5.3 质心更新后偏离单位球面,下一轮迭代距离漂移

现象:第一轮聚类正常,第二轮开始距离矩阵数值分布明显变化,最终收敛到一个解释不了的划分。

原因:K-means 的质心更新是算术平均,两个单位向量的平均向量模长小于 1。而 QK-means 的距离计算把它当作单位向量处理,等于强制把质心投影到单位球面上。这相当于算法在迭代过程中静默换了一个质心定义,聚类目标不稳定,结果自然不对。

解决:质心更新后立即重新归一化。这个做法的数学含义是:QK-means 实际收敛到的是球面 K-means 的解——用余弦相似度而不是欧氏距离做聚类。如果业务场景需要保留模长信息,需要在经典侧把质心模长作为额外标量传入距离公式,即 d² = ‖a‖² + ‖b‖² − 2‖a‖‖b‖·fidelity。但工程上我推荐直接归一化,实现简单且和 swap test 的物理语义一致。

5.4 模拟器在 19 个量子比特以上变慢,整个实验跑不完

现象:单个 swap test 线路跑得很快,但放进 n×k×iterations 的嵌套循环后,总时间指数上涨,一个 200 条文本的小实验要跑几个小时。

原因:AerSimulator 的 statevector 模拟每次运行都会重新初始化完整的 2ⁿ 维状态向量,19 个量子比特对应 2¹⁹ ≈ 52 万个复数状态分量,虽然单次毫秒级,但乘上上千次调用后累加效应非常明显。

解决:先做维度降维,用 PCA 把嵌入压到 32 维甚至 8 维验证流程,再逐步升维。量子线路的基本参数是 log₂d,从 384 维降到 32 维,量子比特数从 19 降到 11,模拟开销接近减半。另一个常见做法是把同一个质心对多个样本的距离估计打包进一次批处理,减少线路构造的开销。如果只是演示流程,16 个量子比特以内的配置足够说明问题。

6. 从模拟器到硬件的三步验证:自适应 shots 与距离矩阵自检

确定 QK-means 在模拟器上稳定之后,还需要一套可以在真实硬件上迁移的验证方法。第一步是自适应 shots 校准。固定 shots 对距离矩阵中不同区域误差不均匀,尤其是内积接近 0.5 的样本对,误差显著大于接近 0 或 1 的区域。做法是设定一个目标方差,对每个样本对做循环:估计一次保真度,算方差,超过阈值就把 shots 翻倍重跑,直到达标或达到上限。

def adaptive_fidelity(vec_a, vec_b, base_shots=2048, target_var=1e-4, max_shots=65536): shots = base_shots while shots <= max_shots: f = swap_test_fidelity(vec_a, vec_b, shots=shots) var = 4 * (f + 1) / 2 * (1 - (f + 1) / 2) / shots # P(0) 方差近似换算 if var <= target_var or shots >= max_shots: return f, var shots *= 2

这段代码把 shots 从 2048 开始按需翻倍,直到保真度估计的方差落在目标区间。核心逻辑是用概率估计的方差公式反推采样需求,避免对所有样本对一刀切地使用大 shots 浪费模拟时间。真实硬件上跑的时候,自适应校准能显著节约量子资源,因为量子计算的时间成本比模拟器贵得多。

第二步是距离矩阵自检。在进入聚类主循环前,随机抽几十个样本对,同时用经典 NumPy 算出真实距离,和 swap test 的估计值画一张散点图,点应该落在对角线上。这个检查能一次性暴露归一化错误、符号问题和 shots 不足。我跑这类项目时最看重这个检查,因为它直接验证“量子部分说的距离和经典物理意义上的距离是同一个东西”。

第三步是小规模硬件测试。先把量子比特数控制在 7 到 11 个之间,用真实后端跑距离估计,对比模拟器结果。真实硬件上的 swap test 输出会偏离理论值,需要用 mitigated 测量或对已知内积的校准对做标定,把偏差拟合成一条校正曲线。这个曲线不会完美,但能为后续的更大规模实验提供误差上界。经验是:保真度偏差超过 0.1 的硬件配置不要直接上 K-means 主循环,先解决校准问题再继续。希望这个验证框架能在你把 QK-means 推向实际业务前,帮你挡掉那些“模拟器上一切正常、换硬件就翻车”的深夜。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询