SBERT

#2026/01/01 #ai

让句子比较变得又快又准

目录

一、为什么需要SBERT?先看看老方法的问题

1. BERT的尴尬处境

SBERT出现之前,如果我们想用BERT来比较两个句子的相似度,会遇到一个大麻烦:计算开销太大了

传统的做法叫做交叉编码器(Cross-Encoder)

句子A:"我喜欢猫"  
句子B:"我爱小猫"  
       ↓  
把两个句子同时塞进BERT  
       ↓  
BERT处理后输出相似度分数  

{%}

**图 :交叉编码器的架构。两个句子被连接在一起,用 <SEP> 词元分隔,然后作为一个整体输入模型

问题来了:如果你有10000个句子,想找出最相似的两个句子,需要比较多少次?

计算次数 = 10000 × 9999 / 2 = 近5千万次!  

每次比较都要跑一遍BERT,这太慢了!


二、SBERT 的聪明解决方案

核心思想:先转换,再比较

SBERT(Sentence-BERT)的作者想到了一个聪明的办法:

老方法(交叉编码器):  
句子A + 句子B → BERT → 相似度分数  
每对句子都要重新计算  

新方法(SBERT/双编码器):  
句子A → BERT → 嵌入向量A  
句子B → BERT → 嵌入向量B  
然后比较向量A和向量B的距离

关键改进

  • 每个句子只需要处理 一次
  • 得到的嵌入向量可以存起来反复使用
  • 比较向量远比跑 BERT 快得多

{%}

图:原始 sentence-transformers 模型的架构,它采用了孪生网络(也称为双编码器)的结构

三、SBERT的架构设计

1. 从分类头到池化层

旧 BERT的做法

输入句子 → BERT → 分类头 → 输出类别  

但研究人员发现,如果直接用BERT的输出层做平均(或用[CLS]词元)来生成句子嵌入,效果竟然还不如简单的词向量平均(比如GloVe)!

SBERT的改进

  • 移除分类头
  • 在BERT的最终输出层上使用平均池化
  • 通过池化生成固定大小的句子嵌入向量

关于 分类头 和 池化 ,详见 4. 分类头、池化、CLS

2. 孪生网络架构(Siamese Network)

SBERT使用了一个巧妙的孪生架构

        句子A                句子B  
          ↓                    ↓  
    BERT (模型1)         BERT (模型2)  
          ↓                    ↓  
    词元嵌入              词元嵌入  
          ↓                    ↓  
      池化层                池化层  
          ↓                    ↓  
    句子嵌入u             句子嵌入v  
          ↓                    ↓  
        合并:(u, v, |u-v|)  
          ↓  
       softmax分类  

关键点

  • 两个BERT模型 共享权重(实际上是同一个模型
  • 每个句子独立通过BERT处理
  • 最后比较两个句子的嵌入向量

四、训练方式详解

1. 孪生网络的实际使用

虽然图上画了两个BERT,但实际上只有一个模型

# 伪代码示意  
bert_model = load_bert_model()  

# 处理句子A  
embedding_a = bert_model.encode("句子A")  

# 处理句子B(用同一个模型)  
embedding_b = bert_model.encode("句子B")  

# 计算相似度  
similarity = cosine_similarity(embedding_a, embedding_b)  

因为是同一个模型,所以:

  • 节省内存
  • 保证一致性
  • 可以分别处理句子(不需要同时输入)

2. 训练目标

SBERT通过对比学习训练:

训练数据:  
配对句子 + 标签(相似/不相似)  

例如:  
句子A:"我的狗很可爱"  
句子B:"我有一只狗"  
标签:相似 ✅  

句子A:"我的狗很可爱"  
句子C:"今天天气不错"  
标签:不相似 ❌  

训练过程

  1. 两个句子分别通过BERT
  2. 得到句子嵌入uv
  3. (u, v, |u-v|)拼接起来
  4. 通过 softmax 判断是否相似
  5. 根据预测结果调整 BERT参数

五、SBERT vs 交叉编码器

对比表格

特性交叉编码器SBERT(双编码器)
处理方式两个句子同时输入两个句子分别输入
速度慢 🐢快 🚀
准确率略高略低但可接受
缓存不能缓存可以缓存嵌入向量
适用场景精排(小规模)粗排(大规模)

具体数字对比

假设要在10000个句子中找最相似的:

交叉编码器:  
- 需要比较:50,000,000次  
- 每次都要跑BERT  
- 时间:几小时 ⏰  

SBERT:  
- 先生成10000个嵌入向量(一次性)  
- 然后比较向量(超快)  
- 时间:几分钟 ⚡  

六、代码示例

使用sentence-transformers

from sentence_transformers import SentenceTransformer  

# 1. 加载SBERT模型  
model = SentenceTransformer('all-mpnet-base-v2')  

# 2. 准备句子  
sentences = [  
    "我喜欢猫",  
    "我爱小猫",  
    "今天天气不错"  
]  

# 3. 生成嵌入向量(每个句子只需处理一次)  
embeddings = model.encode(sentences)  

print(embeddings.shape)  # (3, 768) - 3个句子,每个768维  

# 4. 计算相似度  
from sklearn.metrics.pairwise import cosine_similarity  
similarity_matrix = cosine_similarity(embeddings)  

print(similarity_matrix)  
# 前两个句子(都是关于猫)相似度很高  
# 第三个句子(天气)与前两个相似度低  

七、SBERT的优势总结

1. 速度提升

假设BERT处理一个句子需要10ms  

交叉编码器比较1000个句子:  
(1000 × 999 / 2) × 10ms = 约1.4小时  

SBERT:  
生成嵌入:1000 × 10ms = 10秒  
比较向量:几乎瞬间完成  
总时间:约10秒 ⚡  

速度提升了约500倍

2. 可扩展性

因为嵌入向量可以预先计算并存储:

✅ 可以建立向量数据库  
✅ 支持实时搜索  
✅ 方便增量更新  

3. 灵活性

生成的嵌入向量可以用于:

  • 语义搜索
  • 文本聚类
  • 分类任务
  • 推荐系统

八、应用场景

场景1:语义搜索(第8章)

# 用户查询  
query = "如何修复漏水的水龙头"  

# 文档库(已预先生成嵌入)  
documents = [...]  # 海量文档  
doc_embeddings = [...]  # 预先计算好的嵌入  

# 实时搜索(只需处理查询)  
query_embedding = model.encode(query)  
similarities = cosine_similarity([query_embedding], doc_embeddings)  

# 返回最相似的文档  
top_docs = get_top_k(similarities, k=10)  

场景2:文本聚类(第5章)

# BERTopic就使用SBERT作为默认嵌入模型  
from bertopic import BERTopic  

topic_model = BERTopic(  
    embedding_model="all-mpnet-base-v2"  # 使用SBERT  
)  
topics = topic_model.fit_transform(documents)  

九、核心知识点总结

三个关键创新

  1. 池化替代分类头
    • 旧:BERT → 分类头
    • 新:BERT → 池化层 → 固定维度嵌入
  2. 孪生架构
    • 两个BERT共享权重
    • 句子可独立处理
  3. 对比学习训练
    • 相似句子嵌入靠近
    • 不相似句子嵌入远离

与word2vec的联系

SBERT的训练思想其实源自word2vec的对比学习:

word2vec:  
- 正例:句子中相邻的词  
- 负例:随机采样的词  

SBERT:  
- 正例:语义相似的句子对  
- 负例:不相似的句子对  

十、类比理解

把句子比较比作找相似的人

交叉编码器方法:  
每次要比较两个人时,都要重新观察两个人的所有特征  
→ 很准确,但太慢  

SBERT方法:  
先给每个人拍一张"特征照片"(嵌入向量)  
以后比较只需要看照片  
→ 快得多,准确度也不错  

总结一句话:SBERT通过先嵌入、后比较的方式,把句子相似度计算的速度提升了数百倍,同时保持了不错的准确率,是现代语义搜索和文本匹配系统的基础!