2655 字
约 8 分钟
15
S22-B06 · 文本向量入门:embedding 是什么、怎么比相似

"帮我重置一下密码"和"我密码忘了怎么办",字面上只共享"密码"两个字,向量检索却能判为高度相似;反过来,"这个方案很稳"和"这个方案很稳吗",字面几乎一致,语义方向相反。这两件事都说明:embedding 比的不是字,是在一个高维坐标里离得近不近。这篇只讲清三件事——向量是什么、相似怎么算、什么时候它一定会出错。


1. 向量是什么:一句话的坐标

模型把一段文本压成一个定长浮点数组,比如 768 或 1024 个数字。可以把它想成"语义空间里的一次定位":

概念 直观理解 工程含义
维度 坐标轴的数量 由模型固定,不能改
向量值 在这根轴上的偏移 单个数没有含义,只有整体有意义
距离 两个点的远近 近≈语义相关,远≈无关
归一化 把向量缩到长度 1 只保留方向,丢掉长度

关键假设只有一条:训练目标让语义相近的文本在空间里靠近。这个"相近"由训练数据定义——它决定模型擅长什么、不擅长什么,也决定了后面所有的失败模式。


2. 三种相似度,先记住一个等式

import numpy as np

def cos(a, b):        # 余弦:只看夹角
    return float(a @ b / (np.linalg.norm(a) * np.linalg.norm(b)))

def dot(a, b):        # 点积:夹角 + 长度
    return float(a @ b)

def l2(a, b):         # 欧氏距离:越小越近
    return float(np.linalg.norm(a - b))
算法 取值含义 受向量长度影响 常用场景
余弦相似度 -1~1,越大越像 否 通用语义检索
点积 无固定上界 是 归一化向量、部分模型训练目标
欧氏距离 0~∞,越小越近 是 图像、聚类

归一化之后余弦与点积等价:两个长度都是 1 的向量,a·b == cos(a,b)。而且此时欧氏距离与余弦单调对应(‖a-b‖² = 2 - 2cos),三者排序一致。所以工程上常见做法是:入库前统一 L2 归一化,索引里用点积,省一次除法。

a = a / np.linalg.norm(a); b = b / np.linalg.norm(b)
assert abs(dot(a, b) - cos(a, b)) < 1e-6

前提是真的归一化了。混着存归一化和未归一化的向量,点积排序会被长度污染,表现为"长文本永远排前面"。


3. 维度:不是越高越好

维度量级 常见取舍 代价
384 快、省内存,适合短句分类 细粒度语义容易糊
768 / 1024 通用检索的主力区间 平衡
1536 / 3072 及以上 长文本、细粒度区分 存储与计算线性上升

维度是模型训练时定死的,不能事后裁剪(除非模型本身支持 Matryoshka 式的截断维度)。同一语义在不同维度下表达力不同,但"更高维=更准"不成立:维度要和训练目标匹配,一个 1024 维但只在小语料上训过的模型,可能不如 384 维的通用模型。

存储估算很简单:条数 × 维度 × 4 字节(float32)。100 万条 × 768 维 ≈ 3 GB,这只是原始向量,不含索引结构。


4. 模型之间的差异面

差异面 影响 核对方式
语言支持 中文模型对中文语义更敏感 用中文同义句试
最大长度 超长被截断,尾部信息丢失 查模型卡(model card)的 max tokens
是否归一化 影响能否直接用点积 看输出范数是否≈1
训练目标 对称(句子对)vs 非对称(查询-文档) 看是否区分 query/passage
指令前缀 有些模型要求加 query: 之类前缀 查用法示例

最后一行的坑很隐蔽:给"查询-文档"型模型(如部分 BGE/E5 系列)不加前缀,召回会明显变差,而你不会收到任何报错。


5. 文本怎么变成向量

三步:分词 → 截断 → 池化。

from sentence_transformers import SentenceTransformer
m = SentenceTransformer("MODEL_NAME")
v = m.encode(["第一句", "第二句"], normalize_embeddings=True)
print(v.shape, (v ** 2).sum(axis=1))   # (2, D),第二项应≈1
步骤 发生了什么 常见误解
分词 子词切分,中文常按字/词片段 以为按"词"切,其实多是子词
截断 超过最大长度直接切掉 以为会自动摘要
池化 token 向量合成一个句向量(均值/CLS) 以为是"取第一个 token"

截断是最常见的静默失败:一段 3000 字的文档,模型最大长度 512 token,后面的内容根本没进向量。检索时"文档里明明写了"却召不回,多半是这里。


6. 长文本必须分块

策略 做法 风险
按段落 空行/标题切 段落过长仍会截断
按长度 固定 token 数切 句中切断,语义断裂
重叠窗口 相邻块重叠 10%~20% 存储与召回重复上升
语义切分 按句子边界聚合到阈值 实现复杂,效果依文本而定
def chunk(text, size=400, overlap=80):
    step = size - overlap
    return [text[i:i + size] for i in range(0, len(text), step)]

切坏的后果很具体:把"步骤如下"和列表切开,前一块的向量指向"步骤说明",后一块指向"具体操作",两块都不完整。检索命中任一块都只能回答一半。所以块要自带一点上下文(比如每块前面拼上标题),而不是纯按字数硬切。


7. "相似"不等于"相关"

反例 向量表现 为什么错
同一句式不同主题 "如何配置 A" vs "如何配置 B" 很近 句式主导了相似度
否定句 "支持" vs "不支持" 常很近 否定词只改一个方向
反义词对 "高" vs "低" 在部分模型里很近 共现多,方向差异小
数字与专名 "3 天" vs "30 天" 几乎无差别 数值不是语义主成分

这几类不是模型"坏了",而是训练目标不包含这些区分。对策是别把相似度当答案:让它做召回,把判断交给后续环节(重排、规则、关键词过滤)。否定句尤其要用关键词或规则兜底。


8. 跨模型不能混算

混算方式 结果 原因
A 模型存库,B 模型查询 排序接近随机 语义空间不重合
不同维度直接比 报错或错位 维度不同,坐标轴不对应
同家族不同版本 通常也不兼容 权重变了,空间就变了

模型升级必须全量重建索引,不能只换查询侧。常见事故:升级了查询模型,忘了重建库,表现为"上线后检索质量突然崩了",而日志里一切正常。

索引元数据至少要记:model_name、model_version、dim、normalized、prefix 规则。

有了这五项,跨模型混算能在入库时就被拦下来,而不是等检索结果莫名其妙。


9. 最小示例:三句话的相似度

import numpy as np
from sentence_transformers import SentenceTransformer

m = SentenceTransformer("MODEL_NAME")
sents = ["如何重置密码", "我密码忘了怎么办", "如何重置邮箱"]
v = m.encode(sents, normalize_embeddings=True)
print(np.round(v @ v.T, 3))     # 3x3 相似度矩阵

典型结果(具体数值按模型核对):第 1、2 句明显高于第 1、3 句——尽管第 1、3 句字面更像。这就是语义向量的价值。

再观察失败:

neg = ["这个方案支持回滚", "这个方案不支持回滚"]
v = m.encode(neg, normalize_embeddings=True)
print(float(v[0] @ v[1]))       # 常见在 0.8 以上:否定几乎没被区分

换模型再算一遍,数值与排序都可能变,但否定句这个弱点在很多通用模型上仍然存在。结论不是"换个模型就好了",而是"相似度之后必须有一层判断"。


10. 常见坑

坑 表现 修正
拿不同模型的向量硬比 结果随机,时好时坏 固定模型版本,写进索引元数据
长文本直接截断 文档后半段永远召不回 分块 + 每块带标题
以为 0.9 就很准 阈值换模型就失效 阈值按模型和语料实测标定
忽略语言差异 中文查询配英文库,召回差 选多语模型或统一语言
忘了归一化 长文本总排前面 入库前统一 L2
把相似度当相关性 否定句、反义词误判 加关键词/规则/重排兜底

11. 边界

场景 向量能做 该换别的
找同义改写 是 —
精确匹配编号/错误码 不可靠 BM25、精确索引
否定与条件判断 不可靠 规则、小模型分类
多语言检索 取决于模型 多语模型或翻译后检索
排序精排 粗排可用 交叉编码器重排

12. 自测

  1. 为什么归一化之后点积和余弦等价?这个结论在什么情况下失效?
  2. 一个 3000 字的文档用最大长度 512 的模型编码,会丢掉什么?
  3. 分块时为什么要在每块前面拼标题?
  4. 否定句为什么容易被判为高相似?怎么兜底?
  5. 模型升级后只换查询侧会发生什么?
  6. 相似度 0.85 能不能直接当"相关"的判据?为什么?
S22-B06 · 文本向量入门:embedding 是什么、怎么比相似
https://www.clxhxhhr.top/posts/6126/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。