词、token、上下文表示和句向量不是同一个层次的对象。检索前先确认“向量到底怎样生成”,往往比先选向量数据库更重要。
把四个对象分开
| 对象 | 含义 | 需要确认 |
|---|---|---|
| token ID | 词表中的离散编号 | tokenizer 与词表版本 |
| 输入嵌入 | 根据 ID 查得的向量 | 属于哪套模型参数 |
| 上下文表示 | 随当前文本上下文计算的隐藏状态 | 使用哪一层、怎样处理位置和掩码 |
| 句子或片段向量 | 用于整体文本比较的表示 | 训练目标、池化、归一化和查询指令 |
有上下文的表示再做 Mean Pooling,不等同于对一组固定词向量求平均。不能据“均值与顺序无关”就推断所有采用 Mean Pooling 的句向量模型都丢失语序:池化之前的表示可能已经编码了顺序和上下文。
相似度不是自动校准的置信度
Sentence Transformers 支持余弦、点积等相似度;对非零、已做 L2 归一化的向量,点积等于余弦。官方相似度说明
下面只是手工向量的算术实验,不会生成真实文本嵌入,也不证明任何句子的语义关系。
import numpy as np
def unit_rows(matrix):
matrix = np.asarray(matrix, dtype=float)
norms = np.linalg.norm(matrix, axis=1, keepdims=True)
if (norms == 0).any():
raise ValueError("zero vectors have no cosine direction")
return matrix / norms
query = np.array([[1., 1.]])
docs = np.array([[1., 1.], [10., 0.], [-1., -1.]])
dot_scores = (query @ docs.T)[0]
cos_scores = (unit_rows(query) @ unit_rows(docs).T)[0]
assert int(dot_scores.argmax()) == 1
assert int(cos_scores.argmax()) == 0
assert np.isclose(cos_scores[2], -1.0)
print("dot-product and cosine rankings can differ")
向量维数相同只说明数组可以相乘,不说明它们处于同一个语义空间。不同模型、版本、查询前缀或预处理方式得到的向量,不应未经验证就混入一个索引。
给索引保存一份生成约定
我会与每批向量一起记录:模型与 tokenizer 版本、查询和文档的提示格式、最大长度及截断策略、片段划分方式、池化与归一化、距离函数、原文标识与访问权限。
模型或切分规则改变后,通常需要重新向量化,并通过固定的相关性标注集检查召回。高余弦不代表“90%正确”,否定、数值、实体差异和专业术语尤其需要专项测试。检索到相似段落也不等于段落支持最终回答,后者还需要证据核验。
来源与核验边界
本篇结合疏锦行大模型专题《词向量技术演进史》讲义,重组为检索表示的接口约定,并避免“子词彻底消灭所有未知输入”“均值池化必然无用”等绝对表述。向量算术示例已运行;没有训练或评测真实嵌入模型。视频尚未逐节核验,原课件不公开。
文本向量笔记:同样是768维,为什么不能直接放在一起检索
本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
评论交流
欢迎留下你的想法