Embedding 与语义空间
Embedding 把一段文本压成定长实数向量;「相近的两句话向量就近」不是因为模型理解了语义,而是对比学习把训练目标定义成了这样。所以「相似」不等于「相关」:一句话和它的否定形式可能很近,对称相似与非对称检索是两种任务,换 embedding 模型意味着全量重建。
也叫:Embedding · 嵌入 · 向量表示 · 余弦相似度 · cosine · 语义空间 · 对比学习
原理拆解出自 T0-4
这一问最容易答含糊:正确答案不是「因为模型理解了语义」
训练阶段
正样本对「怎么申请年假」配「年假申请流程说明」
负样本对配「打印机维修指南」
反复优化之后
编码器学到映射被判为相关 → 方向接近
检索阶段
在线算query 编码
离线算好,存进向量库文档编码
余弦相似度只看方向夹角,不看长度
Top-K
三个必须记住的已知失真
| 失真 | 为什么会这样 | 该由谁来接 |
|---|---|---|
| 否定不敏感 | 整段话题一致,否定只是一个低权重信号 | 结构化过滤,别指望向量 |
| 精确匹配被稀释 | 年份、型号、金额、人名、错误码在稠密向量里没有专属维度 | 这才是必须搭配 BM25 的根本原因 |
| 一段多义被平均 | 一个 chunk 讲三件事,向量落在三件事的平均位置 | 分块策略:chunk 讲一件事(T2-2) |
所以「近」等于训练数据里被标成正样本的那种关系。理解这一点,开篇三个故障就全部自解:训练数据里几乎没有「一句话和它的否定形式应该被推远」这样的样本,模型自然没学会区分;也几乎没有「查询是关系、答案要跨两篇文档」这样的样本,空间里根本没有对应的点。
归一化之后,余弦相似度等价于点积 —— 这就是向量库通常要求你先归一化、再用内积检索的原因:更快,而且等价。
为什么语义相近的两句话向量就近?
这是本篇最容易答得含糊的一问。正确答案不是「因为模型理解了语义」,而是:
训练阶段
正样本对:("怎么申请年假", "年假申请流程说明") → 损失函数把它们拉近
负样本对:("怎么申请年假", "打印机维修指南") → 损失函数把它们推远
│
▼
反复优化后,编码器学到一种映射:
"训练数据里被判为相关的文本对" → 方向接近
│
检索阶段 ▼
query ──► 编码 ──► 向量 ──┐
├──► 余弦相似度 ──► Top-K
文档 ──► 编码 ──► 向量 ──┘ (文档侧离线算好,存进向量库)所以「近」的定义,等于训练数据里被标成正样本的那种关系。理解这一点,前面三个故障就全部自解了:训练数据里几乎没有「一句话和它的否定形式应该被推远」这样的样本,模型自然没学会区分;也几乎没有「查询是关系,答案要跨两篇文档」这样的样本,模型自然给不出。
import numpy as np
def cosine(a, b):
a, b = np.asarray(a), np.asarray(b)
return float(a @ b / (np.linalg.norm(a) * np.linalg.norm(b)))
# 归一化之后,余弦相似度等价于点积——
# 这就是为什么向量库通常要求你先归一化,然后用内积检索:更快,且等价。
def normalize(v):
v = np.asarray(v)
return v / np.linalg.norm(v)三个必须记住的已知失真
- 否定不敏感。「能报销」和「不能报销」余弦相似度极高,因为整段话的话题一致,否定只是一个低权重信号。
- 精确匹配被稀释。年份、型号、金额、人名、错误码这类 token,在稠密向量里没有专属维度。这正是必须搭配 BM25 的根本原因,而不只是「工程上稳一点」。
- 一段多义会被平均掉。如果一个 chunk 同时讲了三件事,它的向量落在三件事的「平均位置」,结果是对哪一件都不够像。这条把 Embedding 和分块策略直接绑在一起(T2-2)。
以上节选自T0-4 Embedding 与语义空间:为什么「相似」不等于「相关」,读全文能看到前后语境。