中文向量化的尴尬
做知识库检索时,第一步就是把文档切块转成向量。英文世界有现成的 sentence-transformers,中文呢?2023 年初这块还很青涩,我踩了一圈坑,记下来给后来者省时间。当时我们想给内部 Wiki 做语义搜索,先用英文模型试,中文召回烂得一塌糊涂,才意识到"中文 embedding"是个独立问题。
常见模型与选择
当时能用的:
- OpenAI text-embedding-ada-002(2022 年底发布):1536 维,中英文都还行,但要走 API、有数据出境顾虑,且按 token 计费,百万文档embedding 成本不低;
- sentence-transformers 的多语言模型(如 paraphrase-multilingual):本地可跑,中文质量一般,长文本切分后语义容易散;
- 一些学术发布的国产模型还在萌芽,远未普及,当时基本只能等。
我们内网文档涉密,不能用境外 API,所以只能本地多语言模型将就,或者用自己微调。成本与质量的权衡是当时的核心矛盾。
维度选择的权衡
维度不是越高越好。维度高,向量存储大、近邻检索慢,但语义表达力强。我们对比了同一个检索任务(5000 条内部文档,50 个真实 query):
| 维度 | 召回@5 | 向量存储/万条 | 单次检索耗时 |
|---|---|---|---|
| 384 | 0.71 | 15MB | 3ms |
| 768 | 0.82 | 30MB | 6ms |
| 1536 | 0.84 | 60MB | 12ms |
768 到 1536 召回只涨 2 个点,存储翻倍、检索慢一倍。我们选 768,性价比最高。对千万级文档,这个差距会被放大,维度选择更要抠。
评测方法
别只看模型介绍,自己造个小测试集:取 50 个真实 query,人工标注相关段落,算召回率和 MRR。我们写了个小脚本批量跑,避免被宣传数字带偏:
for q in queries:
hits = index.search(embed(q), top_k=5)
mrr += 1.0 / rank_of_first_relevant(hits)
print("MRR =", mrr / len(queries))
实测多语言模型在我们的技术文档上 MRR 只有 0.43,ada-002 有 0.61。差距明显,但我们为了数据不出境,还是选了多语言模型 + 领域微调,把 MRR 拉到 0.55,够用。
中文特有的坑
- 分词:中文没有空格,切块时按字符数切容易把一个词拦腰斩断,语义丢失。我们改成按标点 + 句号切,再滑窗重叠;
- 简繁与全半角:同一术语繁体和简体向量不同,检索对不上,入库前统一转简体、转半角;
- 同义词:中文同义词多,"下单"和"创建订单"模型未必认为相关,得在 query 侧做同义扩展。
工程落地
embedding 不是一次性的,文档更新要增量重算。我们用一个消息队列,文档变更就发事件,消费者重新切块 embedding 写回 ES 8 的 dense_vector 字段。全量首次建索引跑了 6 小时(500 万段),增量基本秒级。
召回阈值怎么定
余弦相似度阈值 0.75 不是拍的。我们把 50 个 query 的召回结果按相似度排序,人工看"从哪个分位开始不相关":发现 0.78 以上基本相关,0.7 以下大半无关。取 0.75 是折中,宁可漏一点也别塞噪声。上线后观察"回答不知道"的比例,如果太高说明阈值太严、召回太少,往下调;如果编造多说明阈值松、噪声进来了,往上调。阈值是个可以持续调的旋钮,不是一次定死。
重排为什么必要
粗排(向量召回 top 10)保的是"不漏",精排保的是"精准"。我们试过只粗排直接取 top 5 进 prompt,结果模型常被排第 4、5 的弱相关内容带偏,答非所问。加了一层 BM25 + 向量混合打分后,最相关的稳定排到前 2,答案质量明显提升。重排成本很低(几毫秒),收益却不小,是性价比很高的一步。
本地化部署的折中选择
因为数据不出境的硬约束,我们最终没用 ada-002,而是用多语言模型在内部 GPU 机器上自建服务。推理延迟比 API 高(单次向量化 80ms vs 30ms),但数据留在内网,合规过关。成本上自建要养 GPU 机器,比 API 按量贵,但对涉密文档这是必选项。选型时"合规"往往先于"效果",这点是公开评测里很少提、企业里却最关键的维度。
维度对检索的影响实测
维度还影响近邻检索的精度。我们测过,384 维在某些长文档上会把语义相近但用词不同的段落排到后面,召回@5 只有 0.71;768 维能更好捕捉"下单"和"创建订单"的语义距离,提到 0.82。所以维度低不是不能⽤,是长尾 query 的召回会差。如果你的场景 query 都很短很标准,384 也够;query 多样口语化,得上 768。这个选择得结合你真实的 query 分布,不能只看模型宣传的维度。
留个问题
关于《Embedding 模型选型与中文效果对比》里这个坑,你当时是怎么处理的?欢迎在评论区聊聊你踩过的类似情况。