sentence-transformers SparseEncoder 检索后端集成指南:Qdrant / Elasticsearch / OpenSearch / Seismic 语义搜索实战

发布时间:2026/9/21 19:04:29
sentence-transformers SparseEncoder 检索后端集成指南:Qdrant / Elasticsearch / OpenSearch / Seismic 语义搜索实战 人工智能NLPEmbedding微调【免费下载链接】sentence-transformersState-of-the-Art Embeddings, Retrieval, and Reranking项目地址https://gitcode.com/gh_mirrors/se/sentence-transformers点击查看免费下载sentence_transformers.sparse_encoder.search_engines是 sentence-transformers 为稀疏嵌入Sparse Embedding提供的开箱即用的检索后端适配层内置semantic_search_qdrant、semantic_search_elasticsearch、semantic_search_seismic、semantic_search_opensearch四个函数分别对接 Qdrant、Elasticsearch、Seismic 与 OpenSearch。本文以该模块为核心结合 search_engines.py 源码、单元测试 与 官方示例完整讲解四个函数的参数、输入输出格式、建索引与检索的内部流程并给出可直接运行的生产级示例代码。一、模块定位让稀疏嵌入直接对接向量数据库SPLADE 等稀疏编码器产出的嵌入与稠密向量不同绝大多数维度为 0只有少量维度对应词表中的 token携带非零权重。若直接以稠密方式存储与检索会浪费大量存储与算力。本模块的目标就是利用稀疏向量的特性把 SparseEncoder 的输出高效地写入主流向量数据库/搜索引擎并完成语义检索。整个模块只有 4 个公开函数统一遵循编码 → 解码部分后端需要→ 检索 → 格式化结果的模式函数对接系统依赖安装输入形态默认连接地址semantic_search_qdrantQdrantpip install qdrant-clientPyTorch COO 稀疏张量http://localhost:6333semantic_search_elasticsearchElasticsearchpip install elasticsearch解码后的(token, value)列表http://localhost:9200semantic_search_opensearchOpenSearchpip install opensearch-py解码后的(token, value)列表http://localhost:9200semantic_search_seismicSeismic内存索引pip install pyseismic-lsr解码后的(token, value)列表无需外部服务从源码结构看search_engines.py四者的函数签名高度一致核心参数包括query_embeddings/query_embeddings_decoded查询的稀疏嵌入必填。Qdrant 版本直接接收 PyTorch COO 稀疏张量其余三个版本接收解码后的list[list[tuple[str, float]]]形如[[(token, 0.8), ...], ...]corpus_embeddings/corpus_embeddings_decoded语料嵌入可选。仅在未提供corpus_index时使用用于首次自动建索引corpus_index已建好的索引对象可选。Qdrant/Elasticsearch/OpenSearch 为(client, index_name)元组Seismic 为SeismicIndex实例。提供后直接复用跳过建索引阶段top_k每个查询返回的结果条数默认10output_index是否在返回值中一并返回索引对象默认False用于只建一次索引、多次复用的交互式检索场景**kwargs透传给对应客户端构造函数的额外参数如verify_certs、timeout等。所有函数统一返回(all_results, search_time)当output_indexTrue时额外追加索引对象组成三元组。all_results为list[list[dict]]每个查询对应一个列表列表内每项为{corpus_id: int, score: float}。二、前置步骤用 SparseEncoder 产出并解码稀疏嵌入在调用检索函数之前需要先用SparseEncoder完成语料与查询的编码。以官方示例中反复使用的naver/splade-cocondenser-ensembledistil模型为例参见 SparseEncoder 使用指南from sentence_transformers import SparseEncoder sparse_model SparseEncoder(naver/splade-cocondenser-ensembledistil) # 1. 编码语料文档输出 COO 稀疏张量 corpus_embeddings sparse_model.encode_document( corpus, convert_to_sparse_tensorTrue, batch_size16, show_progress_barTrue ) # 2. 编码查询同样输出稀疏张量 query_embeddings sparse_model.encode_query(queries, convert_to_sparse_tensorTrue) # 3. 解码为 (token, weight) 列表供 Elasticsearch / OpenSearch / Seismic 使用 corpus_embeddings_decoded sparse_model.decode(corpus_embeddings) query_embeddings_decoded sparse_model.decode(query_embeddings)其中encode_document/encode_query是针对检索场景的专用方法分别自动套用document或passage、corpus与query提示词见 model.py 与 model.pydecode方法把稀疏张量还原为按权重降序排列的(token, weight)元组列表支持top_k截断见 model.pySparseEncoder还提供intersection与sparsity等辅助方法可用于 token 级匹配分析与稀疏度统计见 model.py 与 model.py。注意输入形态差异semantic_search_qdrant直接消费 COO 稀疏张量源码中会显式校验is_sparse与layout torch.sparse_coo而 Elasticsearch、OpenSearch、Seismic 三个函数只接受解码后的(token, value)列表。这是因为后者需要把 token 名直接作为字段/词项写入索引无法从纯数值张量中还原 token 字符串。三、Qdrant 集成semantic_search_qdrantQdrant 原生支持稀疏向量因此semantic_search_qdrant是唯一直接接收 PyTorch 稀疏张量的函数。源码位于 search_engines.py。3.1 输入校验与自动建索引函数首先校验查询张量必须为 1D 或 2D 的 COO 稀疏张量否则抛出ValueError。随后分两条路径执行corpus_index is None首次调用自动创建客户端与集合完成建索引corpus_index已提供直接复用仅执行检索。自动建索引的关键细节client QdrantClient(urlhttp://localhost:6333, **kwargs) collection_name fsparse_collection_{int(time.time())} client.create_collection( collection_namecollection_name, vectors_config{}, # 不启用稠密向量 sparse_vectors_config{text: models.SparseVectorParams( indexmodels.SparseIndexParams(on_diskFalse) )}, )即集合以sparse_collection_时间戳命名仅配置稀疏向量text索引保存在内存on_diskFalse。语料写入采用每批 10000 条的upload_collection批量上传为了高效切分每行非零坐标源码先用np.searchsorted预计算 COOindices中每行的起止位置见 search_engines.py。3.2 查询流程对每个查询函数从 COO 张量中按行抽取(indices, values)构造models.SparseVector后调用client.query_points( collection_namecollection_name, querymodels.SparseVector(indicesq_indices, valuesq_values), limittop_k, usingtext, )命中结果被格式化为[{corpus_id: hit.id, score: hit.score}, ...]最后返回(all_results, search_time)。3.3 完整示例以下是 semantic_search_qdrant.py 的交互式检索完整流程需要本地运行 Qdrant 并安装pip install qdrant-clientimport time from datasets import load_dataset from sentence_transformers import SparseEncoder from sentence_transformers.sparse_encoder.search_engines import semantic_search_qdrant # 1. 加载 natural-questions 数据集10K 条答案作为语料 dataset load_dataset(sentence-transformers/natural-questions, splittrain) num_docs 10_000 corpus dataset[answer][:num_docs] # 2. 初始查询 queries dataset[query][:2] # 3. 加载模型并编码语料 sparse_model SparseEncoder(naver/splade-cocondenser-ensembledistil) corpus_embeddings sparse_model.encode_document( corpus, convert_to_sparse_tensorTrue, batch_size16, show_progress_barTrue ) # 4. 循环检索索引只建一次之后通过 corpus_index 复用 corpus_index None while True: start_time time.time() query_embeddings sparse_model.encode_query(queries, convert_to_sparse_tensorTrue) print(fEncoding time: {time.time() - start_time:.6f} seconds) results, search_time, corpus_index semantic_search_qdrant( query_embeddings, corpus_indexcorpus_index, corpus_embeddingscorpus_embeddings if corpus_index is None else None, top_k5, output_indexTrue, ) print(fSearch time: {search_time:.6f} seconds) for query, result in zip(queries, results): print(fQuery: {query}) for entry in result: print(f(Score: {entry[score]:.4f}) {corpus[entry[corpus_id]]}, corpus_id: {entry[corpus_id]}) print() queries [input(Please enter a question: )]四、Elasticsearch 集成semantic_search_elasticsearchElasticsearch 通过rank_features字段类型原生支持稀疏向量。semantic_search_elasticsearch的实现位于 search_engines.py输入为解码后的(token, value)列表。4.1 自动建索引rank_features 映射首次调用时自动创建索引sparse_index_时间戳映射结构如下es.indices.create( indexindex_name, body{ mappings: { properties: { tokens: {type: rank_features}, # 稀疏向量的关键rank_features id: {type: keyword}, } } }, )写入阶段有两个值得注意的实现细节token 名中的.会被替换为_。源码注释明确说明 Elasticsearch doesnt handle . in the token names因此dict(corpus_embeddings_decoded[i])中所有 token 键执行str(k).replace(., _)见 search_engines.py文档以每批 1000 条通过helpers.bulk批量写入_id即语料下标最后调用es.indices.refresh使新写入立即可搜。4.2 查询rank_feature saturation每个查询被拆解为若干rank_feature子句权重通过boost注入should_clauses [] for token, weight in query_tokens.items(): should_clauses.append({ rank_feature: {field: ftokens.{token}, saturation: {}, boost: weight} }) query {size: top_k, query: {bool: {should: should_clauses, minimum_should_match: 1}}} result es.search(indexindex_name, bodyquery)命中结果转换为[{corpus_id: int(hit[_id]), score: hit[_score]}, ...]。4.3 完整示例语义搜索示例 中 Elasticsearch 的用法如下需要本地运行 Elasticsearch 并安装pip install elasticsearchimport time from datasets import load_dataset from sentence_transformers import SparseEncoder from sentence_transformers.sparse_encoder.search_engines import semantic_search_elasticsearch dataset load_dataset(sentence-transformers/natural-questions, splittrain) corpus dataset[answer][:10_000] queries dataset[query][:2] sparse_model SparseEncoder(naver/splade-cocondenser-ensembledistil) corpus_embeddings sparse_model.encode_document( corpus, convert_to_sparse_tensorTrue, batch_size16, show_progress_barTrue ) corpus_embeddings_decoded sparse_model.decode(corpus_embeddings) corpus_index None while True: query_embeddings sparse_model.encode_query(queries, convert_to_sparse_tensorTrue) query_embeddings_decoded sparse_model.decode(query_embeddings) results, search_time, corpus_index semantic_search_elasticsearch( query_embeddings_decoded, corpus_embeddings_decodedcorpus_embeddings_decoded if corpus_index is None else None, corpus_indexcorpus_index, top_k5, output_indexTrue, ) print(fSearch time: {search_time:.6f} seconds) for query, result in zip(queries, results): print(fQuery: {query}) for entry in result: print(f(Score: {entry[score]:.4f}) {corpus[entry[corpus_id]]}, corpus_id: {entry[corpus_id]}) print() queries [input(Please enter a question: )]五、OpenSearch 集成semantic_search_opensearchsemantic_search_opensearch与 Elasticsearch 版本高度相似源码位于 search_engines.py同样使用rank_features映射、同样的sparse_index_时间戳命名与每批 1000 条的helpers.bulk写入。核心区别在于查询语法——OpenSearch 使用专用的neural_sparse查询类型query { size: top_k, query: {neural_sparse: {tokens: {query_tokens: query_tokens}}}, } result os_client.search(indexindex_name, bodyquery)此外OpenSearch 路径不做.替换该逻辑仅存在于 Elasticsearch 实现中。依赖为pip install opensearch-py官方示例注明需要OpenSearch v2.15.0。OpenSearch 示例semantic_search_opensearch.py还有一个值得借鉴的进阶用法使用RouterSparseStaticEmbedding构造查询走静态稀疏嵌入、文档走 SpladePooling的非对称模型实现查询侧零推理开销详见 READMEfrom sentence_transformers.sparse_encoder.modules import Router, SparseStaticEmbedding, SpladePooling, Transformer from sentence_transformers.sparse_encoder.search_engines import semantic_search_opensearch model_id opensearch-project/opensearch-neural-sparse-encoding-doc-v3-distill doc_encoder Transformer(model_id, transformer_taskfill-mask) router Router.for_query_document( query_modules[ SparseStaticEmbedding.from_json(model_id, tokenizerdoc_encoder.tokenizer, frozenTrue), ], document_modules[ doc_encoder, SpladePooling(max, activation_functionlog1p_relu), ], ) sparse_model SparseEncoder(modules[router], similarity_fn_namedot) corpus_embeddings sparse_model.encode_document( corpus, convert_to_sparse_tensorTrue, batch_size32, show_progress_barTrue ) corpus_embeddings_decoded sparse_model.decode(corpus_embeddings) # 后续检索循环与 Elasticsearch 示例一致六、Seismic 内存索引集成semantic_search_seismicSeismic 与前三个后端不同它不依赖任何独立服务直接在内存中构建索引并完成检索源码位于 search_engines.py适合大规模语料的单机高速检索。依赖安装命令为pip install pyseismic-lsr。6.1 建索引流程首次调用时函数创建SeismicDataset逐条调用add_document写入文档token 键以np.array(..., dtypestring_type)存储string_type由get_seismic_string()决定权重以np.float32存储dataset SeismicDataset() for idx in tqdm(range(num_vectors), descAdding documents to Seismic): tokens dict(corpus_embeddings_decoded[idx]) dataset.add_document( str(idx), np.array(list(tokens.keys()), dtypestring_type), np.array(list(tokens.values()), dtypenp.float32), ) corpus_index SeismicIndex.build_from_dataset(dataset, **index_kwargs)index_kwargs可透传给build_from_dataset官方文档列出centroid_fraction、min_cluster_size、summary_energy、nknn、knn_path、batched_indexing、num_threads等选项。6.2 批量检索与默认参数检索通过batch_search一次处理全部查询且内置两个默认值query_cut10、heap_factor0.7仅在用户未显式传入search_kwargs时生效。search_kwargs还支持n_knn、sorted、num_threads等 Seismic 原生参数。值得注意的坑Seismic 返回的结果乱序且无命中的查询会返回不带查询 ID 的空列表。因此源码先把结果数组按num_queries预分配为[[] for _ in range(num_queries)]再依据每条结果内携带的查询 ID 归位而不是依赖返回顺序见 search_engines.py。6.3 测试佐证search_engines 单元测试 用 Stub 替身覆盖了 Seismic 的三个关键行为可直接作为行为契约参考无命中的查询返回空列表[]不会报错乱序归位即使 Seismic 按2 → 无 → 0的顺序返回all_results仍严格按查询下标0 → 1 → 2排列全部无命中所有查询都返回空列表。6.4 完整示例import time from datasets import load_dataset from sentence_transformers import SparseEncoder from sentence_transformers.sparse_encoder.search_engines import semantic_search_seismic dataset load_dataset(sentence-transformers/natural-questions, splittrain) corpus dataset[answer][:10_000] queries dataset[query][:2] sparse_model SparseEncoder(naver/splade-cocondenser-ensembledistil) corpus_embeddings sparse_model.encode_document( corpus, convert_to_sparse_tensorTrue, batch_size16, show_progress_barTrue ) corpus_embeddings_decoded sparse_model.decode(corpus_embeddings) corpus_index None while True: query_embeddings sparse_model.encode_query(queries, convert_to_sparse_tensorTrue) query_embeddings_decoded sparse_model.decode(query_embeddings) results, search_time, corpus_index semantic_search_seismic( query_embeddings_decoded, corpus_embeddings_decodedcorpus_embeddings_decoded if corpus_index is None else None, corpus_indexcorpus_index, top_k5, output_indexTrue, ) print(fSearch time: {search_time:.6f} seconds) for query, result in zip(queries, results): print(fQuery: {query}) for entry in result: print(f(Score: {entry[score]:.4f}) {corpus[entry[corpus_id]]}, corpus_id: {entry[corpus_id]}) print() queries [input(Please enter a question: )]七、统一返回格式与索引复用机制四个函数严格遵守同一套返回契约这是该模块最核心的工程价值第一项all_results类型为list[list[dict[str, int | float]]]。外层列表长度等于查询数量内层列表按相关性降序排列每项为{corpus_id: int, score: float}其中corpus_id即语料在原始corpus列表中的下标可直接用它回查原始文档内容第二项search_time秒float仅统计检索耗时不含编码与建索引耗时第三项可选当output_indexTrue时返回索引对象——Qdrant 为(QdrantClient, collection_name)Elasticsearch/OpenSearch 为(Elasticsearch/OpenSearch, index_name)Seismic 为SeismicIndex。借助output_indexTrue与corpus_index参数可以优雅地实现**建一次索引、无限次检索**首次调用传入corpus_embeddings并开启output_index之后每次调用只传corpus_index即可完全跳过建索引阶段。上文四个示例的while True循环正是这一模式的典型应用。八、错误处理与依赖管理从源码可以总结出模块统一的两类错误处理策略依赖缺失每个函数在函数体内延迟导入对应客户端库而非模块顶部导入缺失时抛出带安装指引的ImportErrorQdrantpip install qdrant-clientElasticsearchpip install elasticsearchOpenSearchpip install opensearch-pySeismicpip install pyseismic-lsr。输入不合法抛出ValueError例如Qdrant 要求稀疏张量is_sparse且layout torch.sparse_coo且维度必须为 1D 或 2D解码列表版本要求严格为list[list[tuple[str, float]]]结构即外层列表的每个元素是列表列表内元素是长度为 2 的元组corpus_embeddings与corpus_index必须至少提供一个否则报Either corpus_embeddings or corpus_index must be provided。得益于延迟导入设计即使未安装任何后端客户端sentence_transformers的导入与模型加载也不受影响测试也据此采用 monkeypatch 注入 Stub 模块的方式验证行为见 test_search_engines.py。九、选型建议与延伸阅读综合源码与官方示例四种后端的适用场景可概括为Qdrant原生稀疏向量支持最直接输入无需解码适合已部署 Qdrant 或偏好张量直通的团队Elasticsearch / OpenSearch适合已有 ES/OpenSearch 基础设施、需要与既有文档检索体系融合的场景OpenSearch 的neural_sparse查询对稀疏语义检索有专门优化Seismic完全内存化、无需外部服务适合单机大语料的高吞吐检索场景官方示例称其相对常见 IVF 方案有明显性能优势该结论来自示例对 Seismic 论文 的转述选用前可结合自身数据规模验证。关于稀疏嵌入的更多背景什么是稀疏嵌入、为什么适合语义检索、手动检索与 token 归因分析可阅读 语义搜索示例 README 与 semantic_search_manual.py若需了解 SparseEncoder 的完整 API 与训练方法可进一步参考 包参考索引 与 稀疏编码器使用指南稠密嵌入的语义搜索入门可参考 SentenceTransformer 语义搜索示例。赞分享人工智能NLPEmbedding微调【免费下载链接】sentence-transformersState-of-the-Art Embeddings, Retrieval, and Reranking项目地址https://gitcode.com/gh_mirrors/se/sentence-transformers点击查看免费下载相关推荐基于 SparseEncoder 的稀疏向量语义搜索实战从手动检索到 Qdrant、OpenSearch、Elasticsearch、Seismic 与 SPLADE-index 集成基于 SparseEncoder 的稀疏向量语义搜索实战从手动检索到 Qdrant、OpenSearch、Elasticsearch、Seismic 与 SP人工智能NLPEmbedding微调Sentence Transformers SparseEncoder 应用实战稀疏嵌入计算、语义搜索与向量数据库集成指南Sentence Transformers SparseEncoder 应用实战稀疏嵌入计算、语义搜索与向量数据库集成指南 导读 本文围绕 sentence人工智能NLPEmbedding微调sentence-transformers 检索工具集util.retrieval实战指南语义搜索、释义挖掘与社区检测sentence transformers 检索工具集util.retrieval实战指南语义搜索、释义挖掘与社区检测 导读 本文全面剖析 sentenc人工智能NLPEmbedding微调上一篇AppRetention安全与隐私保活功能会泄露数据吗一文读懂风险与防范下一篇TeleChat-52B-pt安全使用指南遵循社区许可协议的最佳实践创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考