
做语义检索和 RAG 问答项目时很多人第一步都是直接加载一个预训练嵌入模型把 query 和文档分别编码成向量再用余弦相似度做召回。这套流程在通用语料上效果还不错但一遇到医疗问答、法律条款、电商客服这类垂直领域数据排名效果就会明显下降。要解决这个问题通常不是换一个更大的模型而是用你自己的业务数据去训练或微调嵌入模型。本文围绕 Sentence Transformers 展开先讲清楚单向量与多向量嵌入模型的区别再分别给出数据准备、训练、评估的完整代码和参数说明最后整理高频踩坑点与工程建议。适合正在做语义检索、RAG 问答、FAQ 匹配、向量召回的同学收藏。1. 背景与核心概念1.1 单向量嵌入模型的局限所谓单向量嵌入模型指的是输入一段文本后模型把整句话“压缩”成一个固定维度的向量比如 768 维或 1024 维。Sentence Transformers 里的SentenceTransformer类干的就是这件事底层是一个 BERT 类的预训练语言模型经过池化层后输出一个句子向量。检索阶段我们把所有文档离线编码成向量构造向量索引线上把 query 编码成向量后用 Faiss、Milvus 这类向量检索工具做 ANN 召回最后计算余弦相似度排序。单向量模型最大的优点是快一个句子一次前向计算就得到结果向量化之后还能利用成熟的 ANN 索引支撑百万千万级别的召回。但它的短板也很明显句子被压成一个点大量词级、短语级的细粒度信息都会丢失。比如“苹果公司发布了新款手机”和“苹果是一种很常见的水果”这两句话主题完全不同但在单向量空间里可能距离很近因为“苹果”这个词在语义表征中占了很大权重。再比如“孩子咳嗽可以吃川贝吗”和“孩子咳嗽不能吃川贝”只差一个字单向量模型很容易把它们的向量拉得很近导致召回结果出错。这类问题在垂直领域尤其突出。通用预训练模型是在互联网语料上学习的业务场景里大量专有名词、特定表达、文档格式它都没见过直接拿来用召回率和准确率都会打折扣。因此掌握嵌入模型的训练与微调能力是做好检索系统的一项基本功。1.2 多向量嵌入模型是什么多向量嵌入模型与单向量模型最大的区别在于输入不再被压缩成一个向量而是保留为一组向量。以最具代表性的 ColBERT 为例query 里的每个 token 都会得到一个对应的 token 级向量文档侧同样保留 token 级向量。计算相似度时不使用余弦相似度而是使用“晚期交互”late interaction机制也就是 MaxSim 算法对 query 中的每一个 token 向量分别与文档中所有 token 向量计算相似度取最大值作为这个 query token 对该文档的匹配得分把所有 query token 的最大得分累加得到最终相关度分数。这种设计的优势是保留了词级匹配能力。query 中的“苹果”会重点与文档中的“苹果”进行精确匹配而不是在全局向量中平均掉。“手机屏幕碎了怎么修复”和“屏幕碎了自己换还是找维修店”这种表达层面不完全一致、但关键词高度相关的句子在多向量模型下往往能获得比单向量模型更高的匹配分数。当然多向量模型的代价也很直观文档需要存储的不再是一个向量而是一串 token 向量存储成本显著上升线上打分时也不能直接走 cosine 索引需要专门的检索策略和打分逻辑计算量更大。可以把它理解为用存储和计算换精度。1.3 为什么要自己训练或微调很多同学以为嵌入模型是“开箱即用”的实际上通用模型在垂直领域的效果往往只能算“能用”离“好用”还有距离。最典型的改进方式就是微调用自己的业务数据对预训练模型做少量迭代训练让模型记住领域内的专有词汇和语义模式。多向量模型也是一样。早期很多团队直接拿现成的 ColBERT checkpoint 用但遇到中文领域数据时效果不一定理想。一方面ColBERT 原始模型主要以英文为主另一方面领域数据里的 query 习惯、文档结构、口语化表达都不同必须经过微调才能贴合实际业务。换句话说训练和微调是让嵌入模型从“通用”走向“专用”的关键一环。即使你不想从零训练一个大模型掌握基于 Sentence Transformers 的微调流程也能用少量标注数据快速提升检索质量。这就是本文想帮你解决的问题。2. 环境准备与版本说明2.1 安装依赖建议使用独立的 Python 虚拟环境避免与其他项目依赖冲突。Python 版本推荐 3.9 到 3.11PyTorch 使用 2.x 版本。下面是完整的安装命令conda create -n st python3.10 -y conda activate st # 按你的 CUDA 版本选择 pytorch 安装方式这里以 cu121 为例 pip install torch --index-url https://download.pytorch.org/whl/cu121 pip install -U sentence-transformers pip install datasets安装完成后可以运行下面的命令确认版本python -c import sentence_transformers, torch; print(ST:, sentence_transformers.__version__); print(PyTorch:, torch.__version__)2.2 版本与兼容性说明Sentence Transformers 目前有两个常用的 API 版本。2.x 时代主要使用model.fit()方法代码示例在网上非常常见3.x 版本开始引入了SentenceTransformerTrainer更接近 Hugging Face Transformers 的 Trainer 风格训练流程更规范支持日志、checkpoint、恢复训练等能力。本文示例以 3.x 版本为主同时保留一段 2.x 的fit()写法。如果你的环境是 2.x建议直接使用旧版 API如果你刚接触这个库建议直接安装 3.x 并使用新版 Trainer。不同版本的参数名有差异跑代码前先确认自己的版本避免照抄报错。另外模型默认从 Hugging Face Hub 下载国内网络不稳定时可能会失败可以提前配置镜像源例如设置HF_ENDPOINT环境变量为可用的镜像地址或者从 ModelScope 下载模型后切换到本地路径加载。2.3 硬件要求与显存预估微调一个 BERT 规模的嵌入模型显存需求并不算特别夸张。以BAAI/bge-small-zh-v1.5约 118M 参数为例在 batch size 为 16、最大序列长度 128 的情况下8G 显存的 GPU 基本可以跑起来如果使用更大的模型比如BAAI/bge-large-zh-v1.5建议至少 16G 显存。如果你的机器没有 GPU先用很小的数据集体验流程也是可以的但训练时间会非常慢。显存不够时优先考虑四件事调小 batch size、开启梯度累积、开启 fp16 混合精度、使用 LoRA 这类参数高效微调方法。这些内容会在后文展开。3. 核心原理单向量与多向量的区别3.1 从 token 到句子向量池化BERT 类预训练模型接收一段文本后会为每个 token 输出一个高维向量。这些向量包含上下文语义信息但直接拿来当句子表示并不合适因为不同句子的 token 数量不同无法统一比较。因此单向量嵌入模型会再接一个池化层把 token 向量聚合成一个固定长度的句子向量。常用的池化方式有两种mean_pooling对 token 向量求平均过滤掉 padding 位置cls_pooling直接取[CLS]位置的向量作为句子表示。Sentence Transformers 里的池化逻辑封装在Pooling模块中正常情况下你不需要自己实现。理解这段原理是有意义的因为多向量模型的核心恰恰就是“跳过池化、保留 token 向量”。3.2 late interaction 与 MaxSim 打分多向量模型的代表性打分方式是 MaxSim。下面用一个简化例子来说明query: 怎么 修复 手机 屏幕 doc: 屏幕 碎了 怎么 办 query[0]怎么 → 与 doc 中所有 token 算相似度取最大值 query[1]修复 → 与 doc 中所有 token 算相似度取最大值 query[2]手机 → 与 doc 中所有 token 算相似度取最大值 query[3]屏幕 → 与 doc 中所有 token 算相似度取最大值 最终分数 4 个最大值的总和可以看到late interaction 没有把整句话压成一个向量而是让每个 query token 单独去文档里找最相关的 token。这种机制对短语匹配、同义词替换、词