归一化与点积在 Embedding 中的作用

1. 核心结论

在 embedding 向量检索场景中,归一化(Normalization)和点积(Dot Product)是配合使用的一对操作:

先对向量做归一化,把它变成长度为 1 的单位向量;再通过点积计算两个归一化向量的相似度,此时点积的结果就直接等价于 cosine 相似度。

这套组合是向量检索里最常见、最基础的相似度计算方式,被广泛用于长期记忆、语义搜索、推荐系统等场景。

2. 归一化是什么

归一化指的是把向量按比例缩放,使其长度(模长)变为固定值(通常为 1),但方向保持不变。

2.1 计算公式

对向量 v=v1,v2,...,vnv = v_1, v_2, \\dots, v_nv=v1,v2,...,vn,先计算模长:

∥v∥=v12+v22+⋯+vn2 \|v\| = \sqrt{v_1^2 + v_2^2 + \dots + v_n^2} ∥v∥=v12+v22+⋯+vn2

再让每个分量除以模长:

v^=v1∥v∥,v2∥v∥,...,vn∥v∥ \hat{v} = \left\\frac{v_1}{\\\|v\\\|}, \\frac{v_2}{\\\|v\\\|}, \\dots, \\frac{v_n}{\\\|v\\\|}\\right v^=∥v∥v1,∥v∥v2,...,∥v∥vn

归一化后的向量 v^\hat{v}v^ 满足 ∥v^∥=1\|\hat{v}\| = 1∥v^∥=1。

2.2 代码实现

ts 复制代码
function normalizeVector(vector: number[]): number[] {
  const norm = Math.sqrt(vector.reduce((sum, value) => sum + value * value, 0));

  if (norm === 0) {
    return vector;
  }

  return vector.map((value) => value / norm);
}

2.3 举例说明

向量 v=3,4v = 3, 4v=3,4 的模长:

∥v∥=32+42=9+16=25=5 \|v\| = \sqrt{3^2 + 4^2} = \sqrt{9 + 16} = \sqrt{25} = 5 ∥v∥=32+42 =9+16 =25 =5

归一化后:

v^=35,45=0.6, 0.8 \hat{v} = \left\\frac{3}{5}, \\frac{4}{5}\\right = 0.6,\\ 0.8 v^=53,54=0.6, 0.8

验证新向量模长:

∥v^∥=0.62+0.82=0.36+0.64=1=1 \|\hat{v}\| = \sqrt{0.6^2 + 0.8^2} = \sqrt{0.36 + 0.64} = \sqrt{1} = 1 ∥v^∥=0.62+0.82 =0.36+0.64 =1 =1

模长确实变为 1,方向没有改变。

2.4 为什么需要对零向量做保护

ts 复制代码
if (norm === 0) {
  return vector;
}

如果输入文本为空或没有任何 token 命中分桶,向量可能是全零向量 [0, 0, ..., 0],此时模长为 0,除以 0 会得到 NaN。这里直接返回原向量,避免程序崩溃。

3. 点积是什么

点积是两个向量之间的运算:把对应位置的分量两两相乘,再把所有结果相加,得到一个标量(单个数字)。

3.1 计算公式

对于向量 A=a1,a2,...,anA = a_1, a_2, \\dots, a_nA=a1,a2,...,an 和 B=b1,b2,...,bnB = b_1, b_2, \\dots, b_nB=b1,b2,...,bn

A⋅B=a1b1+a2b2+⋯+anbn=∑i=1naibi A \cdot B = a_1 b_1 + a_2 b_2 + \dots + a_n b_n = \sum_{i=1}^{n} a_i b_i A⋅B=a1b1+a2b2+⋯+anbn=i=1∑naibi

3.2 代码实现

ts 复制代码
function cosineSimilarity(left: number[], right: number[]): number {
  if (left.length !== right.length) {
    throw new Error(`Vector dimension mismatch: ${left.length} !== ${right.length}`);
  }

  let score = 0;

  for (let index = 0; index < left.length; index += 1) {
    score += left[index] * right[index];
  }

  return score;
}

3.3 举例说明

设 A=1,2,3A = 1, 2, 3A=1,2,3,B=4,5,6B = 4, 5, 6B=4,5,6

A⋅B=(1×4)+(2×5)+(3×6)=4+10+18=32 A \cdot B = (1 \times 4) + (2 \times 5) + (3 \times 6) = 4 + 10 + 18 = 32 A⋅B=(1×4)+(2×5)+(3×6)=4+10+18=32

3.4 点积的几何意义

点积本来不是专门为"方向相似度"设计的,它更本质的作用是衡量:

一个向量沿着另一个向量方向产生了多少有效贡献。

它的几何公式是:

A⋅B=∥A∥×∥B∥×cos⁡(θ) A \cdot B = \|A\| \times \|B\| \times \cos(\theta) A⋅B=∥A∥×∥B∥×cos(θ)

其中:

  • θ\thetaθ:向量 AAA 和 BBB 之间的夹角。
  • cos⁡(θ)\cos(\theta)cos(θ):两个向量方向的接近程度。
  • ∥A∥\|A\|∥A∥:向量 AAA 的长度。
  • ∥B∥\|B\|∥B∥:向量 BBB 的长度。

也可以把点积理解为:

A⋅B=A 在 B 方向上的投影长度×∥B∥ A \cdot B = \text{A 在 B 方向上的投影长度} \times \|B\| A⋅B=A 在 B 方向上的投影长度×∥B∥

而 AAA 在 BBB 方向上的投影长度是:

∥A∥cos⁡(θ) \|A\|\cos(\theta) ∥A∥cos(θ)

所以:

A⋅B=(∥A∥cos⁡(θ))×∥B∥ A \cdot B = \left(\|A\|\cos(\theta)\right) \times \|B\| A⋅B=(∥A∥cos(θ))×∥B∥

这说明点积同时包含两部分信息:

text 复制代码
方向是否接近
+
两个向量本身有多长

3.5 点积原本用来做什么

点积常用于衡量"有效贡献"。例如物理中的功:

W=F⋅d W = F \cdot d W=F⋅d

其中:

  • FFF:力的向量。
  • ddd:位移向量。
  • WWW:力沿位移方向实际做的功。

如果力和位移方向一致,做功最大;如果垂直,力没有沿位移方向产生有效贡献;如果方向相反,则做负功。

这正是点积的典型用途:

判断一个向量在另一个向量方向上到底贡献了多少。

3.6 如何借助点积判断方向相似

方向相似真正来自公式中的 cos⁡(θ)\cos(\theta)cos(θ)。

情况 夹角 θ\thetaθ cos⁡(θ)\cos(\theta)cos(θ) 方向关系
方向完全相同 0°0°0° 111 最相似
方向有一定偏移 60°60°60° 0.50.50.5 部分相似
方向垂直无关 90°90°90° 000 基本无关
方向完全相反 180°180°180° −1-1−1 相反

问题在于,原始点积还会受到 ∥A∥\|A\|∥A∥ 和 ∥B∥\|B\|∥B∥ 的影响。

例如两个向量方向完全相同,但长度不同:

A=3,0,B=2,0 A = 3, 0, \quad B = 2, 0 A=3,0,B=2,0

A⋅B=3×2=6 A \cdot B = 3 \times 2 = 6 A⋅B=3×2=6

如果 BBB 变得更长:

B=10,0 B = 10, 0 B=10,0

A⋅B=3×10=30 A \cdot B = 3 \times 10 = 30 A⋅B=3×10=30

这两个例子里方向完全一样,但点积结果不同,原因是长度不同。

所以,如果只想判断方向相似度,就需要先归一化:

∥A∥=1,∥B∥=1 \|A\| = 1, \quad \|B\| = 1 ∥A∥=1,∥B∥=1

代入点积公式:

A⋅B=1×1×cos⁡(θ)=cos⁡(θ) A \cdot B = 1 \times 1 \times \cos(\theta) = \cos(\theta) A⋅B=1×1×cos(θ)=cos(θ)

这时点积就不再受向量长度影响,只剩下方向关系。

因此在 embedding 检索中:

text 复制代码
未归一化点积:方向相似 + 向量长度
归一化后点积:只表示方向相似

这就是为什么向量检索通常先做归一化,再用点积判断 query embedding 和 memory embedding 的语义方向是否接近。

4. 归一化 + 点积 = Cosine 相似度

4.1 标准 cosine 相似度公式

cos⁡(θ)=A⋅B∥A∥×∥B∥ \cos(\theta) = \frac{A \cdot B}{\|A\| \times \|B\|} cos(θ)=∥A∥×∥B∥A⋅B

不做归一化时,每次计算相似度都需要额外求两个向量的模长,再做一次除法。

4.2 归一化后的简化

如果 AAA 和 BBB 都已经归一化,即 ∥A∥=∥B∥=1\|A\| = \|B\| = 1∥A∥=∥B∥=1,公式简化为:

cos⁡(θ)=A⋅B1×1=A⋅B \cos(\theta) = \frac{A \cdot B}{1 \times 1} = A \cdot B cos(θ)=1×1A⋅B=A⋅B

也就是说:归一化之后,点积的结果直接等于 cosine 相似度,不需要再做除法。

4.3 为什么要这样设计

这样设计有两个好处:

  • 计算更快:省去了每次查询时重复计算模长和除法的开销,尤其在大规模向量检索场景下能显著提升性能。
  • 逻辑更简单:向量库和检索代码只需要维护一种运算(点积),不用同时处理模长计算和归一化逻辑的耦合。

工程上通常的做法是:写入时统一归一化,查询时只做点积

5. 在长期记忆 Embedding 流程中的位置

结合长期记忆系统的检索流程,归一化和点积分别出现在不同阶段:

text 复制代码
Memory Object
  ↓
buildEmbeddingText(memory)          // 拼接语义文本
  ↓
embeddingModel.embed(text)          // 生成原始向量
  ↓
normalizeVector(vector)             // 归一化:写入阶段
  ↓
写入向量索引 / 向量库


用户 query
  ↓
embeddingModel.embed(query)         // 生成原始向量
  ↓
normalizeVector(vector)             // 归一化:查询阶段
  ↓
cosineSimilarity(queryVec, memVec)  // 点积:相似度计算
  ↓
按分数排序,返回 topK

5.1 写入阶段:归一化

每条长期记忆写入向量索引前,都需要对其 embedding 做归一化,确保后续所有向量都处于同一个"单位长度"的比较基准上。

5.2 查询阶段:归一化 + 点积

用户的 query 生成向量后,同样需要归一化,然后与索引中的每条记忆向量做点积,点积结果就是该记忆与当前 query 的语义相似度分数。

5.3 排序阶段

拿到所有候选记忆的相似度分数后,按分数从高到低排序,取 topK 作为召回结果,再结合 importance、关键词召回等信号做进一步的 rerank。

6. 归一化的处理时机

归一化不一定是 embedding 模型自动完成的。它可能发生在三个地方:模型内部、业务代码、向量库。

6.1 模型输出已归一化

有些 embedding 模型或 SDK 会直接返回单位向量。

如果模型文档有类似说明:

text 复制代码
returned embeddings are normalized
unit norm vectors
cosine similarity recommended

就不需要业务代码再归一化。

6.2 归一化在业务代码中处理

很多系统会在写入和查询时自己归一化,这是最可控的方式:

ts 复制代码
// 写入时
const memoryEmbedding = normalizeVector(
  await embeddingModel.embed(memoryText)
);

// 查询时
const queryEmbedding = normalizeVector(
  await embeddingModel.embed(query)
);

不管 embedding 模型内部是否已经归一化,在外部再归一化一次是安全的(模度等于 1 的向量归一化后仍是它自身)。

6.3 归一化在向量库内部处理

有些向量库支持直接配置相似度度量:

text 复制代码
metric = cosine
metric = dot / inner_product
metric = l2
  • 选择 cosine:向量库可能内部处理归一化,也可能只是按 cosine 公式计算。需看文档确认。
  • 选择 dot / inner_product:等价于直接用点积,建议业务代码自己归一化,否则长度会干扰相似度结果。
  • 选择 l2:是 L2 距离度量,归一化后 L2 和 cosine 相关但不完全等价,需具体场景具体判断。

6.4 如何判断模型输出是否已归一化

如果模型文档不写明,可以直接测一下返回向量的模长:

ts 复制代码
function vectorNorm(vector: number[]): number {
  return Math.sqrt(vector.reduce((sum, value) => sum + value * value, 0));
}

结果接近 1(如 0.9999991.000001)说明已归一化;远大于 1(如 12.428.9)则没有归一化。

6.5 工程上最稳妥的做法

不论 embedding 模型是否内部归一化,建议写入和查询两侧都显式归一化一次

text 复制代码
写入时:
embeddingModel.embed(text) -> normalizeVector() -> 写入向量库

查询时:
embeddingModel.embed(query) -> normalizeVector() -> 点积检索

这样可以保证:

A⋅B=cos⁡(θ) A \cdot B = \cos(\theta) A⋅B=cos(θ)

不会被向量长度影响。

归一化由谁处理 场景 注意事项
embedding 模型自动归一化 模型文档说明返回单位向量 需确认文档,不能假设
业务代码自己归一化 metric = dot,或对结果要求确定 最可控,建议默认采用
向量库内部处理 metric = cosine 不同库行为不一,需看文档

7. 核心作用总结

操作 作用 解决的问题
归一化 把向量统一缩放为单位长度,只保留方向信息 消除向量长度差异对相似度判断的干扰
点积 计算两个向量对应分量乘积之和 提供衡量向量方向接近程度的数值
归一化 + 点积 点积结果直接等价于 cosine 相似度 省去模长计算和除法,简化相似度计算流程

8. 一句话总结

归一化让向量只保留方向、去掉长度差异;点积衡量两个向量方向的接近程度;当写入和查询的向量都提前归一化后,点积的计算结果就直接是 cosine 相似度,这也是长期记忆向量检索里判断"语义是否相关"最核心、最基础的数学工具。

相关推荐
Designerpupu3 小时前
纸巾亚马逊详情页怎么做?3 步用 GPT 生成高转化产品图片
gpt·ai·photoshop·ps·跨境电商·亚马逊·详情页
曦尧4 小时前
开源后台编程 Agent 系统 background-agents:架构拆解与落地判断
ai·自动化
阿木实验室6 小时前
AI融入硬件产品的思考和实战:LQ10图数传的AI智能配置
ai·无人机·图数传
Dragon Wu6 小时前
ComfyUI SUPIR图片扩图插件集成报错解决
ai
Smoothcloud润云6 小时前
训练一个7B参数的大模型需要多少张GPU?大概要跑多久?
人工智能·深度学习·ai·开源·gpu算力
俊哥V6 小时前
每日 AI 研究简报 · 2026-07-13
人工智能·ai
flying jiang7 小时前
OMC 的执行原理是什么
ai
User_芊芊君子7 小时前
实测参赛|用飞算JavaAI快速落地企业项目招投标管理系统
人工智能·ai·amd
胖胖大王叫我来巡山8 小时前
Understand-Anything的运行模式
ai