如何利用Python NumPy计算两个向量之间的余弦相似度?

2026-06-03 134319 日常编程

最可靠的方法是手动用 numpy.dot 和 numpy.linalg.norm 计算余弦相似度:cosθ = dot(a, b) / (norm(a) * norm(b)),需确保向量为1D、非零且已归一化,避免隐式行为和形状错误。

numpy.dotnumpy.linalg.norm 手动计算最可靠

直接调用现成的余弦相似度函数容易踩坑——比如 sklearn.metrics.pairwise.cosine_similarity 默认返回二维数组,而你可能只想要一个标量;又或者输入向量没做归一化,结果就错了。手动算反而更可控。

核心公式是:cosθ = dot(a, b) / (norm(a) * norm(b)),只要两个向量非零,这个值就在 [-1, 1] 范围内。

  • numpy.dot(a, b) 计算点积,要求两个向量维度一致且为 1D(shape 为 (n,)
  • numpy.linalg.norm(a) 默认计算 L2 范数,等价于 sqrt(sum(a**2))
  • 如果输入是行向量(shape (1, n)),先用 a.flatten()a.squeeze() 降维,否则 dot 可能返回矩阵而非标量
  • 务必检查分母是否为 0:任一向量全为 0 时 norm 返回 0,会导致除零警告甚至 NaN

避免 np.innernp.vdot 的隐式行为

np.inner 对 1D 向量等价于 dot,但对高维数组行为不同(例如对 shape (n, 1) 的列向量,inner 会广播并返回 shape (n, n) 结果);np.vdot 则自动对第一个参数取共轭,对实数虽无影响,但一旦后续接入复数数据就会出错。

  • 坚持用 np.dot + np.linalg.norm 组合,语义清晰、行为稳定
  • 若已知向量为列向量(如从 pandas.DataFrame.values 直接取的),别直接传给 dot,先做 a.ravel()
  • 不要依赖 np.matmul:它不支持纯 1D 输入,必须至少有一维是 2D,易触发 ValueError: matmul: Input operand X does not have enough dimensions

处理批量向量对时注意广播与形状对齐

如果要一次性算多对向量(比如 100 个 query 向量 vs 100 个 doc 向量),别用循环套 dot,而是用广播或矩阵乘法加速。

Python 3.14.3

微软官方的 Python 扩展,是 VS Code 安装量最高的扩展(209M+)。集成 IntelliSense(通过 Pylance)、调试(通过 Python Debugger)、代码检查、格式化、重构和单元测试等功能。支持 Jupyter Notebook、虚拟环境管理和多 Python 版本切换。

  • 两组行向量:shape 分别为 (m, d)(n, d),可用 result = (A @ B.T) / (np.linalg.norm(A, axis=1, keepdims=True) * np.linalg.norm(B, axis=1, keepdims=True).T)
  • 如果只是单对向量但存成二维(如 a.shape == (1, d)),别用 @ 直接乘,先 a[0] 取出再算,否则结果 shape 是 (1, 1),不是你要的标量
  • keepdims=Truenorm 中很关键:它让除法能正确广播;漏掉会导致 ValueError: operands could not be broadcast together

NaN 或 inf 出现时优先查向量范数和原始数据

得到 naninf 不代表公式写错了,大概率是输入本身有问题。

  • 运行 np.any(np.isnan(a)) or np.any(np.isinf(a)) 检查原始向量
  • 打印 np.linalg.norm(a)np.linalg.norm(b),看是否为 0 或极小值(如 1e-18),这说明向量几乎为零,数值不稳定
  • 避免用 np.nan_to_num 粗暴替换:它会把 nan 变成 0,但余弦相似度为 0 和未定义是两回事;更稳妥的是提前过滤或打日志告警

实际用的时候,最常被忽略的是向量形状和零向量检查——尤其当数据来自文件读取或模型输出时,看似是向量,实则多了一维或含缺失值。公式再简单,输进去的数据不对,结果就不可信。

如何利用Python Django的Include标签拆分大型模板文件?

{%include%}能拆分模板但需注意变量作用域、路径规则(仅从templates根目录解析)和循环中重复渲染的性能问题,非万能解法。 直接说结论:用{%include%}能拆分模板,但必须注意变量作用域、路径解析规则和循环内重复渲染的性能陷阱——它不是“无脑切文件”的万能解药。 include标签怎么写才不报TemplateDoesNotExist Django的{%include%}默认只在...

如何在Python中利用NumPy实现矩阵的奇异值分解(SVD)?

numpy.linalg.svd返回U、s、Vh三元组,s为降序一维数组,Vh是V的共轭转置;full_matrices=False更高效;重建需构造匹配形状的对角矩阵;处理小奇异值需设阈值防除零;结果符号不唯一。 NumPy的numpy.linalg.svd是最直接、最可靠的SVD实现方式,它返回标准的U、s、Vh三元组,无需额外封装或手动拼接。 如何调用numpy.linalg.svd并理解返...

为什么Python环境下TensorFlow的计算速度低于预期?

根本原因是num_parallel_calls默认为None导致map()串行阻塞,应设为tf.data.AUTOTUNE;正确流水线顺序是map()→cache()→shuffle()→batch()→prefetch()。 tf.data流水线卡在map()里,GPU利用率忽高忽低 常见现象是训练刚开始几轮很快,之后map()耗时陡增,prefetch()像没起作用。根本原因不是CPU不够,而...

在Python中如何利用PyTorch实现Transformer位置编码的计算?

torch.nn.Embedding不适合实现正弦位置编码,因其引入可学习参数且无法保证公式结构;正确做法是手动向量化计算sin/cos编码并注册为buffer。 PyTorch中torch.nn.Embedding不适合直接实现正弦位置编码 正弦位置编码(SinusoidalPositionalEncoding)是原始Transformer论文中提出的固定编码方式,它不参与训练、无参数、依赖序列...

如何在Python中通过计算欧氏距离实现基础的最近邻分类算法?

欧氏距离计算函数怎么写才不出错? 直接用math.sqrt和sum手动算平方和开方最稳妥,避免依赖未安装的库。常见错误是传入维度不一致的向量,比如训练样本是4维,测试样本只给了3个值——运行时会报IndexError或得到错误结果。 实操建议: 在计算前用len(vec1)==len(vec2)做校验,不等就抛出ValueError 别用numpy.linalg.norm,除非你确定项目里已统一用...

为什么在Python 3.11中升级NumPy可以显著提升科学计算速度?

NumPy版本升级本身不提升计算速度,真正起效的是Python3.11解释器的导入优化、conda+MKL链接及自适应解释器对胶水代码的加速;import变快≠计算变快。 NumPy在Python3.11中“升级”本身并不会显著提升科学计算速度——真正起作用的是解释器层面的加载与执行机制变化,而你误以为是NumPy版本升级带来的收益。 importnumpy变快≠计算变快 Python3.11对模...

怎么在Python中实现对大规模文本的关键词提取_利用Jieba库的TF-IDF

直接用jieba.analyse.extract_tags处理大规模文本会卡住或内存爆掉,因其默认一次性加载全部文档构建全局IDF,导致idf_dict膨胀、无法复用模型,且未分词前正则分割易触发回溯爆炸。 为什么直接用jieba.analyse.extract_tags处理大规模文本会卡住或内存爆掉 因为默认的jieba.analyse.extract_tags会把全部文档一次性读入内存,构建全...

如何在 Tkinter 中安全地将走法计算移至独立线程

本文详解如何通过单例工作线程+队列机制,避免多线程竞争与ui冻结,实现在tkinter棋类游戏中异步执行ai走法计算,同时保障界面响应性与逻辑一致性。 本文详解如何通过单例工作线程+队列机制,避免多线程竞争与ui冻结,实现在tkinter棋类游戏中异步执行ai走法计算,同时保障界面响应性与逻辑一致性。 在Tkinter应用中直接将耗时的AI计算(如跳棋的多层搜索与评估)放入新线程看似可行,但若未加...