PaddleNLP 语义匹配模型库 semantic_search 模块详解:ERNIE 双塔与交叉编码器实战
2026/9/24 18:02:43 网站建设 项目流程

PaddleNLP 语义匹配模型库 semantic_search 模块详解:ERNIE 双塔与交叉编码器实战

【免费下载链接】PaddleNLPEasy-to-use and powerful LLM and SLM library with awesome model zoo.项目地址: https://gitcode.com/gh_mirrors/pa/PaddleNLP

semantic_search 是 PaddleNLP transformers 目录下的语义匹配(语义检索)模型模块,围绕 ERNIE 预训练模型封装了 ErnieEncoder、ErnieDualEncoder 与 ErnieCrossEncoder 三类组件,分别对应向量编码、双塔召回与交叉排序三种经典语义匹配范式。读完本文,你将掌握这三类编码器的构造参数、推理与训练调用方式、RocketQA 预训练模型的选择方法,并能将其接入端到端语义检索系统。

模块定位与文档入口

在 PaddleNLP 中,paddlenlp.transformers.semantic_search是专门承载语义匹配模型实现的子模块,其 API 文档入口为 paddlenlp.transformers.semantic_search,对应实现文件为 semantic_search/modeling.py。

模块对外导出的类通过__all__明确声明(见init.py):

__all__ = ["ErnieDualEncoder", "ErnieCrossEncoder", "ErnieEncoder"]
  • ErnieEncoder:单塔语义编码器,基于 ERNIE 输出句向量,是另外两个类的基础组件;
  • ErnieDualEncoder:双塔(Dual-Encoder)模型,同时封装 Query 编码器与 Title/Passage 编码器,用于向量召回(Retrieval);
  • ErnieCrossEncoder:交叉(Cross-Encoder)模型,将 Query 与 Passage 拼接后输入单个 ERNIE,用于精排(Ranking)。

三者共用同一套 ERNIE 骨干网络,体现了"召回用双塔、精排用交叉"的工业级语义检索标准流程。

ErnieEncoder:语义编码基座

ErnieEncoder 继承自 ErniePretrainedModel,以 ErnieConfig 为配置入口,内部封装一个完整的 ErnieModel(见 modeling.py):

class ErnieEncoder(ErniePretrainedModel): def __init__(self, config: ErnieConfig, output_emb_size: int | None = None): super(ErnieEncoder, self).__init__(config) self.ernie = ErnieModel(config) dropout = config.classifier_dropout if config.classifier_dropout is not None else 0.1 self.dropout = nn.Dropout(dropout) self.classifier = nn.Linear(config.hidden_size, config.num_labels) # Compatible to ERNIE-Search for adding extra linear layer if output_emb_size is not None and output_emb_size > 0: weight_attr = paddle.ParamAttr(initializer=paddle.nn.initializer.TruncatedNormal(std=0.02)) self.emb_reduce_linear = paddle.nn.Linear(config.hidden_size, output_emb_size, weight_attr=weight_attr)

其结构要点如下:

  1. 骨干网络self.ernie = ErnieModel(config)提供完整的 ERNIE Transformer 编码能力;
  2. Dropout 与分类头:默认 Dropout 为config.classifier_dropout,未配置时回退到0.1classifier是一个hidden_size -> num_labels的线性层,供交叉编码分类使用;
  3. 输出降维层(emb_reduce_linear):当传入output_emb_size > 0时,额外增加一个使用TruncatedNormal(std=0.02)初始化的线性层,将 hidden_size 维向量压缩到指定维度。这是为了兼容 ERNIE-Search 等场景——向量维度越小,ANN 索引的内存占用与检索开销越低;
  4. 初始化钩子_init_weights将 LayerNorm 的 epsilon 统一设置为1e-12,保证数值稳定性。

forward返回(sequence_output, pool_output),其中sequence_output是全序列的 token 级向量,pool_output是句级池化输出。get_pooled_embedding正是从sequence_output[:, 0](即 [CLS] token 向量)提取句向量,这也是 BERT 系模型最常见的句向量取法。

ErnieDualEncoder:双塔向量召回模型

构造参数详解

ErnieDualEncoder 将两个 ErnieEncoder 封装进同一个 nn.Layer(见 modeling.py),使得 Query 向量与 Passage 向量可以通过一个模型同时计算、联合训练。其完整构造签名如下:

def __init__( self, query_model_name_or_path=None, title_model_name_or_path=None, share_parameters=False, output_emb_size=None, dropout=None, reinitialize=False, use_cross_batch=False, ):
参数类型默认值说明
query_model_name_or_pathstrNoneQuery 端 ERNIE 的预训练模型名或本地路径
title_model_name_or_pathstrNoneTitle/Passage 端 ERNIE 的预训练模型名或本地路径
share_parametersboolFalse为 True 时两端共享同一套参数(self.title_ernie = self.query_ernie),大幅减少参数量
output_emb_sizeintNone输出句向量维度,>0 时启用 emb_reduce_linear 降维
dropoutfloatNoneDropout 比率(目前透传给 ErnieEncoder 由其 config 决定)
reinitializeboolFalse为 True 时执行init_epsilon_weights,将 LayerNorm epsilon 重置为1e-5,用于兼容 RocketQA v2 权重初始化
use_cross_batchboolFalse训练时是否跨 batch 聚合负样本(见下文)

构造逻辑:

  • query_model_name_or_path非空,加载 Query 编码器;
  • share_parameters为 True,Title 编码器直接复用 Query 编码器;
  • 否则若提供了title_model_name_or_path,独立加载 Title 编码器;
  • 断言至少一个编码器存在(At least one of query_ernie and title_ernie should not be None),防止空模型。

官方文档示例:向量提取

模型 Docstring 中给出了最小可用示例,完整继承如下(见 modeling.py):

import paddle from paddlenlp.transformers import ErnieDualEncoder, ErnieTokenizer model = ErnieDualEncoder("rocketqa-zh-dureader-query-encoder", "rocketqa-zh-dureader-para-encoder") tokenizer = ErnieTokenizer.from_pretrained("rocketqa-zh-dureader-query-encoder") inputs = tokenizer("Welcome to use PaddlePaddle and PaddleNLP!") inputs = {k: paddle.to_tensor([v]) for (k, v) in inputs.items()} # Get query embedding query_embedding = model.get_pooled_embedding(**inputs) # Get title embedding title_embedding = model.get_pooled_embedding(**inputs, is_query=False)

这里分别加载了 RocketQA 的 Query 编码器与 Passage 编码器,同一个 tokenizer 编码出的文本,通过is_query开关即可从对应塔中拿到向量。get_pooled_embedding内部(见 modeling.py)会断言is_query与初始化方式一致,避免误用未加载的塔。

批量向量化:get_semantic_embedding

针对文档库批量建索引的场景,模型提供了生成器方法get_semantic_embedding(data_loader)(见 modeling.py):

def get_semantic_embedding(self, data_loader): self.eval() with paddle.no_grad(): for batch_data in data_loader: input_ids, token_type_ids = batch_data input_ids = paddle.to_tensor(input_ids) token_type_ids = paddle.to_tensor(token_type_ids) text_embeddings = self.get_pooled_embedding(input_ids, token_type_ids=token_type_ids) yield text_embeddings

它以yield方式逐 batch 产出句向量,自动切到eval()并关闭梯度,适合配合 DataLoader 将海量段落写入 ANN 索引库。

相似度计算:cosine_sim

cosine_sim同时计算 Query 与 Title 的句向量,并输出两者的内积(此处输入向量均已归一化,内积等价于余弦相似度,见 modeling.py):

query_cls_embedding = self.get_pooled_embedding( query_input_ids, query_token_type_ids, query_position_ids, query_attention_mask ) title_cls_embedding = self.get_pooled_embedding( title_input_ids, title_token_type_ids, title_position_ids, title_attention_mask, is_query=False ) cosine_sim = paddle.sum(query_cls_embedding * title_cls_embedding, axis=-1)

该方法可直接用于离线评测或在线打分,返回 batch 维度的相似度张量。

训练前向:三元组与交叉批负样本

forward接收一组(query, pos_title, neg_title)三元组(见 modeling.py),分别提取 Query、正例、负例向量后执行:

  1. 合并候选paddle.concat([pos_title_cls_embedding, neg_title_cls_embedding], axis=0)将正负 Passage 拼接;
  2. 交叉批负样本:若use_cross_batch=True,先通过paddle.distributed.all_gather聚合所有卡上的候选向量,再paddle.concat,使每张卡的 Query 都能看到全局 batch 的负样本,从而显著扩大负样本池、提升训练信号;
  3. 打分与标签logits = paddle.matmul(query_cls_embedding, all_title_cls_embedding, transpose_y=True)计算 Query 与全部候选的内积得分矩阵;标签为paddle.arange(...)构造的对角索引,即"Query 只与同 batch 的正例匹配";
  4. 指标与损失:通过paddle.metric.accuracy计算命中率,F.cross_entropy计算多分类交叉熵损失,返回{"loss": loss, "accuracy": accuracy}
  5. 预测模式:当is_prediction=True时,只对(query, pos_title)对做paddle.dot并返回{"probs": ..., "q_rep": ..., "p_rep": ...},便于上线阶段提取向量或直接打分。

需要说明的是,labels计算中引用了self.rank,从代码结构看这是为分布式场景设计的字段,实际使用时需结合paddle.distributed的 rank 信息配合use_cross_batch使用。

ErnieCrossEncoder:交叉精排模型

双塔模型以向量相似度召回候选,但 Query 与 Passage 之间缺乏细粒度交互;交叉编码器将两者拼接后一起送入 ERNIE,通过自注意力充分建模交互语义,精度更高但计算开销更大,因此常用于精排阶段(见 modeling.py)。

构造方式只需指定一个预训练模型:

def __init__(self, pretrain_model_name_or_path, num_classes=2, reinitialize=False, dropout=None):
  • num_classes默认 2(二分类:相关/不相关),透传给内部 ErnieEncoder 的 classifier;
  • reinitialize=True时同样将 LayerNorm epsilon 重置为1e-5,兼容 RocketQA v2。

官方 Docstring 示例(完整继承):

import paddle from paddlenlp.transformers import ErnieCrossEncoder, ErnieTokenizer model = ErnieCrossEncoder("rocketqa-zh-dureader-cross-encoder") tokenizer = ErnieTokenizer.from_pretrained("rocketqa-zh-dureader-cross-encoder") inputs = tokenizer("你们好", text_pair="你好") inputs = {k: paddle.to_tensor([v]) for (k, v) in inputs.items()} # Get embedding of text pair. embedding = model.matching(**inputs)

注意这里tokenizer通过text_pair参数把 Query 与 Passage 拼接成单条输入,这正是交叉编码的典型用法。

交叉编码器提供了三个匹配方法,对应不同的特征来源与模型代际:

方法特征来源适用场景说明
matchingpool_output(句级池化)RocketQA v1 点式预测返回 softmax 后取probs[:, 1],即"相关"类的概率
matching_v2sequence_output[:, 0](CLS token)RocketQA v2 列式预测用 CLS token 特征替代句级池化
matching_v3pool_output(句级池化)ERNIE-Search 列式预测直接返回未过 softmax 的 logits

三个方法都先取特征 → Dropout →classifier线性层。matching默认返回probs[:, 1](二分类相关概率),传入return_prob_distributation=True时返回完整概率分布。

forward默认调用matching并返回完整概率分布;若提供labels,则同时计算accuracyF.cross_entropy损失并返回{"loss": ..., "accuracy": ...},从而支持有监督精排训练。

RocketQA 系列预训练模型与配置

semantic_search 模块的所有示例均基于 RocketQA 系列预训练模型,其完整配置注册在 ernie/configuration.py 的PRETRAINED_INIT_CONFIGURATION中。以中英文档检索常用的rocketqa-zh-dureader-query-encoder为例(见 configuration.py):

{ "attention_probs_dropout_prob": 0.1, "hidden_act": "relu", "hidden_dropout_prob": 0.1, "hidden_size": 768, "initializer_range": 0.02, "max_position_embeddings": 513, "num_attention_heads": 12, "num_hidden_layers": 12, "type_vocab_size": 2, "vocab_size": 18000, "pad_token_id": 0 }

可见该系列采用 12 层、12 头、768 维的 ERNIE 结构,中文词表大小 18000,激活函数为 relu。仓库中注册的 RocketQA 模型按用途可分为:

  • 双塔召回模型rocketqa-zh-dureader-query-encoder/rocketqa-zh-dureader-para-encoder(中文 DuReader 场景)、rocketqa-v1-marco-query-encoder/rocketqa-v1-marco-para-encoder(英文 MS MARCO 场景),以及rocketqa-zh-base/medium-*-encoder等不同规格;
  • 交叉精排模型rocketqa-zh-dureader-cross-encoderrocketqa-v1-marco-cross-encoder,以及rocketqa-base/medium/mini/micro/nano-cross-encoder多档位版本(见 configuration.py)。

模型名作为from_pretrained的入参即可自动下载对应权重与配置,无需手工组织文件。选型时可按"小模型(nano/mini)用于原型验证、大模型(base)用于追求精度"的原则取舍。

端到端实战:双塔召回 + 交叉精排的语义检索系统

semantic_search 模块的模型可直接服务于 PaddleNLP 生态中的完整检索系统。仓库在 slm/pipelines/examples/semantic-search/README.md 中给出了"召回(DensePassageRetriever)+ 精排(ErnieRanker)+ ANN 索引 + WebUI"的端到端方案,其核心思路正是语义检索:不依赖字面关键词,而是捕捉 Query 的真实意图,在高维向量空间中度量相似度。

一键运行示例(源自 semantic_search_example.py):

# GPU 环境 export CUDA_VISIBLE_DEVICES=0 python examples/semantic-search/semantic_search_example.py --device gpu --search_engine faiss # 仅 CPU 环境(耗时较长) unset CUDA_VISIBLE_DEVICES python examples/semantic-search/semantic_search_example.py --device cpu --search_engine faiss

示例脚本暴露的常用参数与默认值如下:

参数默认值说明
--devicegpu运行设备,可选 cpu/gpu
--index_namedureader_indexANN 索引名
--search_enginefaiss检索引擎,可选 faiss/milvus
--max_seq_len_query64Query 最大长度
--max_seq_len_passage256Passage 最大长度
--retriever_batch_size16建索引时的向量化 batch 大小
--query_embedding_modelrocketqa-zh-nano-query-encoderQuery 编码模型
--passage_embedding_modelrocketqa-zh-nano-query-encoderPassage 编码模型(nano 版默认共享)
--embedding_dim312ANN 索引向量维度
--pooling_modecls_token句向量池化方式(max/mean/mean_sqrt_len/cls)
--model_typeernie模型类型(ernie_search/ernie/bert/neural_search)

其中embedding_dimoutput_emb_size的对应关系值得注意:当model_typeernie_searchneural_search时,示例会将output_emb_size设为embedding_dim,即启用本模块emb_reduce_linear降维层,把向量压缩到索引所需维度以降低存储与检索开销(见 semantic_search_example.py 中DensePassageRetriever的构造逻辑)。

完整的 Web 检索系统由三部分组成:基于 ElasticSearch 的 ANN 服务(默认端口 9200)、基于 RestAPI 的模型服务、基于 Streamlit 的 WebUI,详细搭建步骤可参考 README.md 第 3.4 节,多路召回(关键词 + 语义)方案见 Multi_Recall.md,自定义模型训练与接入流程见 Neural_Search.md。

小结

paddlenlp.transformers.semantic_search以 ERNIE 为骨干,提供了语义匹配领域最核心的三种建模范式:

  • ErnieEncoder提供统一的句向量提取与分类能力,output_emb_size降维机制让向量可以灵活适配 ANN 索引维度;
  • ErnieDualEncoder将 Query/Passage 双塔封装为一模型,支持参数共享、三元组训练、跨 batch 负样本与 cosine 打分,是向量召回的现成组件;
  • ErnieCrossEncoder通过 Query-Passage 拼接交互实现精排,matching/matching_v2/matching_v3覆盖 RocketQA v1、v2 与 ERNIE-Search 三代特征方案。

配合仓库预置的 RocketQA 系列权重与 semantic-search 示例,开发者可以快速搭建"双塔召回 + 交叉精排"的完整语义检索链路,将上述模型直接用于文本相似度计算、FAQ 匹配、文档检索等生产场景。

【免费下载链接】PaddleNLPEasy-to-use and powerful LLM and SLM library with awesome model zoo.项目地址: https://gitcode.com/gh_mirrors/pa/PaddleNLP

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询