登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  科技周边 >  人工智能

Embedding 向量归一化怎样影响相似度计算

来源:17golang原创

时间:2026-10-18 11:35:34 144浏览 收藏

Embedding 做相似度计算时,最容易混淆的是“向量方向相近”和“向量长度较大”。如果直接使用点积,长度也会参与分数;如果先做 L2 归一化,所有向量长度都变成 1,此时点积就与余弦相似度等价。实际项目应该先确认模型和向量库采用的约定,再决定是否归一化,不能只看一个分数名称。

官方资料:https://www.sbert.net/docs/sentence_transformer/usage/semantic_textual_similarity.html

官方任务文档:https://huggingface.co/docs/transformers/main/en/tasks/sentence_transformers

为什么同一个方向的点积还会变大变小

设两个向量为 a 和 b。点积可以写成 a·b = ||a|| ||b|| cos(θ),它同时受夹角和两个向量长度影响。两个文本的语义方向可能接近,但只要其中一个向量的范数更大,点积就可能更高。因此,原始点积回答的是“方向和长度合在一起有多大”,并不只是“方向有多像”。

余弦相似度会除以两个向量的范数:cos(a,b) = (a·b) / (||a|| ||b||)。它把长度因素拿掉,保留夹角信息。对语义检索来说,这通常更适合比较文本方向,但是否应该这样做仍取决于模型训练目标和索引端的距离定义。

原始 Embedding、L2 归一化、单位向量、点积和余弦相似度的关系说明图
图1:Embedding 归一化与相似度度量的关系说明图,区分方向和长度对分数的影响;这是静态说明图,不是运行截图。

L2 归一化为什么能让点积等价于余弦

L2 归一化就是把向量除以自身的 L2 范数:a' = a / ||a||。归一化后的 a' 和 b' 长度都是 1,所以 a'·b' = cos(θ)。这不是把语义重新编码了一次,而是改变了比较时保留的因素:向量方向保留,长度差异被压平。

Sentence Transformers 的 Normalize 模块正是把 embedding 处理为单位长度向量。官方文档还特别说明:如果模型输出末尾已经有归一化模块,选择 dot 度量即可,因为 cosine 会再次归一化输入,dot 可以少做一次重复工作。

下面的写法把归一化和度量显式放在同一处,避免查询向量和文档向量走不同分支。示例只展示调用关系,不代表已经在本机运行。

from sentence_transformers import SentenceTransformer, SimilarityFunction

model = SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2")
sentences = ["如何重置数据库连接池", "数据库连接池怎样恢复"]

# 让查询和文档使用同一套 L2 归一化约定。
embeddings = model.encode(sentences, normalize_embeddings=True)

# 单位向量的点积就是余弦相似度,避免再次做归一化。
model.similarity_fn_name = SimilarityFunction.DOT_PRODUCT
scores = model.similarity(embeddings[:1], embeddings[1:])
print(scores)

把未归一化点积、余弦和单位向量点积分开

可以把三种结果放在同一张对照表里。未归一化点积保留范数影响;余弦相似度在计算时主动消除范数影响;单位向量点积则把消除范数的动作提前到 embedding 生成或写入向量库之前。

输入状态计算方式分数主要表达注意点
原始向量dot方向与长度的联合贡献长度差异可能改变排序
原始向量cosine方向接近程度计算时会做范数处理
单位向量dot方向接近程度与 cosine 数学等价,适合固定约定
单位向量euclidean单位球面上的距离关系排序方向要按距离大小正确处理

用 Sentence Transformers 时,normalize_embeddings 和 similarity_fn_name 要一起记录。只在写入向量库时归一化、查询时忘记归一化,会让两侧分数失去可比性;反过来,如果模型本身已经输出单位向量,又在检索组件中重复转换,通常只是增加不必要的计算。

from sentence_transformers import SentenceTransformer, util

model = SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2")
sentences = ["向量检索需要统一度量", "检索系统要固定分数定义"]

# 保留一份原始向量,用来观察范数是否参与点积。
raw = model.encode(sentences, normalize_embeddings=False)
# 另一份固定为单位向量,适合与 dot_score 配套。
unit = model.encode(sentences, normalize_embeddings=True)

# cosine 会按公式处理范数;raw_dot 会保留长度影响。
cosine = util.cos_sim(raw[0], raw[1])
raw_dot = util.dot_score(raw[0], raw[1])
# 单位向量点积与 cosine 的语义口径一致。
unit_dot = util.dot_score(unit[0], unit[1])
print(cosine, raw_dot, unit_dot)

在实际检索中怎样选度量

选择顺序应该是“先看模型说明,再看存储约定,最后看查询代码”。有些模型明确按 dot-product 训练或提供已经归一化的向量,这时直接使用 dot 更自然;如果系统以余弦相似度定义阈值,就要确保查询和文档向量都按相同规则计算。不能因为某个库支持 cosine,就把所有模型都无条件改成 cosine。

对于单位向量,欧氏距离与余弦相似度也存在固定的单调关系,因此在同一批单位向量中,按余弦从高到低或按欧氏距离从低到高,通常会得到一致的排序。这个结论依赖“双方都已归一化”,一旦把原始向量混进来,就不能继续套用。

模型训练目标、查询文档向量、单位长度约定与三种检索度量的关系说明图
图2:向量检索度量选择说明图,展示归一化约定与 dot、cosine、euclidean 的关系;这是静态说明图,不是运行截图。

接入前要避免的三个分数误读

  • 把点积当成天然的余弦。只有两个输入都为单位向量时,二者才数学等价。
  • 只归一化一侧。查询和文档必须使用相同的归一化口径,否则阈值和排序都难以解释。
  • 忽略模型训练目标。度量方式是模型和索引契约的一部分,换度量后应重新评估召回与排序,而不是只比较分数大小。

落地时可以把三个字段写进配置或模型卡:是否归一化、使用哪种度量、分数是越大越相似还是越小越接近。这样迁移向量库、替换 embedding 模型或调整阈值时,才不会把不同口径的分数混在一起。

常见问题

归一化会让 embedding 的语义信息丢失吗?

它会舍弃向量长度这一维度信息,但保留方向信息。是否适合舍弃,要看长度是否承载了模型训练希望利用的信号;语义检索不能只凭公式做决定。

已经归一化后还可以调用 cosine 吗?

可以,结果在数学上仍是余弦相似度,但计算时通常会再次处理范数。Sentence Transformers 官方建议在确定输入已归一化时使用 dot,以减少重复操作。

为什么换成 dot 后阈值不能照搬?

如果前后输入状态或分数方向发生变化,阈值就不再代表同一件事。应先固定归一化约定,再在目标数据上重新确定阈值和排序指标。

总结

Embedding 归一化的核心影响只有一句话:它把点积中的向量长度因素固定掉,让单位向量的点积等价于余弦相似度。工程上不要孤立选择 cosine 或 dot,而要同时记录模型训练目标、向量是否归一化、查询与文档是否同口径,以及距离排序方向;这四项一致,分数才有可解释性。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>