作者:来自 Elastic Jeffrey Rengifo
HyDE 将简短查询的语义搜索精度和召回率提升了 50%。以下介绍如何在 Elasticsearch 中结合 Inference API 和 semantic_text 实现它。
Elasticsearch 与业界领先的 Gen AI 工具和提供商进行了原生集成。查看我们的网络研讨会,了解如何超越 RAG 基础,或借助 Elastic 向量数据库构建可用于生产环境的应用。
要为你的使用场景构建最佳搜索解决方案,立即开始免费云试用,或立即在你的本地机器上体验 Elastic 。
假设文档嵌入( HyDE ) 在针对正式文档语料库的简短、随意查询中,将语义搜索的精确率和召回率提高了 50%,而无需重新建立索引或更改你的嵌入模型。该技术通过让一个 LLM 生成一个与你的查询匹配的假设文档,然后使用该文档的嵌入作为搜索向量来实现。这个虚假的文档会被丢弃;最终返回的只有真实结果。本文将展示如何在 Elasticsearch 中使用 Inference API 和 semantic_text 实现 HyDE,通过精确率、召回率和 MRR 与基准结果进行衡量,并决定什么时候值得进行额外的一次 LLM 往返调用。
前置条件
-
Elastic Cloud 集群或 Elasticsearch 9.x+ (开始免费试用)
-
Python 3.9+
-
用于生成假设文档的 OpenAI API 密钥。
什么是 HyDE,以及它为什么有效?
HyDE 通过在嵌入之前生成一篇与语料库具有相同语域的完整长度假设文档,缩小了简短查询和正式文档之间的嵌入差距。
嵌入不仅仅编码主题。它还编码主题、语域、词汇密度以及句子结构,并将所有这些信息封装到一个向量中。一个九个单词的随意查询,例如 “adamw vs Adam transformers”,只携带少量语义信号,因此它的嵌入位于向量空间中一个模糊的区域。一篇 200 个单词、涵盖相同主题的摘要包含了几十个相互强化的术语(权重衰减、梯度更新、收敛、泛化),这些术语能够让它的嵌入定位得更加精确。结果是:针对简短查询进行语义搜索时,可能会偏向松散相关的论文,而不是找到最佳匹配结果。
HyDE 通过让 LLM 在嵌入之前按照与语料库相同的风格撰写一篇文档来缩小这一差距,这种策略可以补充其他用于提升搜索质量的查询重写技术。流程如下:
假设文档不需要在事实层面完全正确。它可能包含错误的细节,这没有关系,因为在提取其嵌入之后,该文档会被丢弃。用户最终只会看到索引中的真实文档。
该技术最早在没有相关性标签的精准零样本密集检索(Gao 等人,2022 年)中被提出。
如何在 Elasticsearch 中设置 HyDE
如果你希望一次性运行所有代码,所有代码片段都可以在配套笔记本中找到。
加载 ML arXiv 数据集
我们从 CShorten/ML-ArXiv-Papers 中抽取 5,000 篇论文:机器学习 arXiv 标题和摘要。这为我们提供了一个正式、技术性的文档语料库,这类语料库正是简短查询最难处理的场景。
`
1. from datasets import load_dataset
3. dataset = load_dataset("CShorten/ML-ArXiv-Papers", split="train")
4. dataset = dataset.shuffle(seed=42).select(range(5000))
5. print(f"Sampled {len(dataset)} papers")
`AI写代码
使用 semantic_text 建立索引
作为一个向量数据库,Elasticsearch 让我们能够在单个系统中处理嵌入生成、存储和搜索。我们使用在 Elastic Cloud 中预先配置好的 .jina-embeddings-v5-text-small推理端点。copy_to 参数允许单个 semantic_text 字段同时覆盖 title 和 abstract,因此这两个字段都会被嵌入,并通过一个向量进行搜索。这篇关于 semantic_text 正式发布的完整介绍涵盖了该字段类型的全部功能,包括语义高亮。
`
1. INDEX_NAME = "arxiv-ml-papers"
3. es_client.indices.create(
4. index=INDEX_NAME,
5. mappings={
6. "properties": {
7. "title": {"type": "text", "copy_to": "semantic_content"},
8. "abstract": {"type": "text", "copy_to": "semantic_content"},
9. "semantic_content": {
10. "type": "semantic_text",
11. "inference_id": ".jina-embeddings-v5-text-small",
12. },
13. }
14. },
15. )
`AI写代码
批量索引全部 5,000 篇论文:
`
1. from elasticsearch import helpers
4. def build_bulk_actions(dataset, index_name):
5. for i, item in enumerate(dataset):
6. yield {
7. "_index": index_name,
8. "_id": i,
9. "_source": {
10. "title": item["title"],
11. "abstract": item["abstract"],
12. },
13. }
16. success, failed = helpers.bulk(
17. es_client,
18. build_bulk_actions(dataset, INDEX_NAME),
19. refresh=True,
20. )
21. print(f"Indexed {success} papers into '{INDEX_NAME}'")
`AI写代码
创建聊天补全推理端点
我们通过 Elasticsearch Inference API 注册一个 OpenAI gpt-4o-mini 端点。通过 Elasticsearch 路由 LLM 调用(而不是直接调用 OpenAI)可以让 API 密钥处理、重试和可观测性都保持在你的集群内部:
`
1. from elasticsearch import NotFoundError
3. HYDE_INFERENCE_ID = "hyde-completion"
5. try:
6. es_client.inference.delete(inference_id=HYDE_INFERENCE_ID)
7. except NotFoundError:
8. pass
10. es_client.inference.put(
11. task_type="completion",
12. inference_id=HYDE_INFERENCE_ID,
13. inference_config={
14. "service": "openai",
15. "service_settings": {
16. "api_key": OPENAI_API_KEY,
17. "model_id": "gpt-4o-mini",
18. },
19. },
20. )
21. print(f"Created inference endpoint: {HYDE_INFERENCE_ID}")
`AI写代码
Elasticsearch 中的基准语义搜索
在引入 HyDE 之前,我们需要一个参考基准。下面是一个使用语义查询的简单语义搜索函数:
`
1. def search(query_text, size=5):
2. response = es_client.search(
3. index=INDEX_NAME,
4. query={
5. "semantic": {
6. "field": "semantic_content",
7. "query": query_text,
8. }
9. },
10. size=size,
11. _source=["title", "abstract"],
12. )
13. return response["hits"]["hits"]
16. def print_hits(hits):
17. for i, hit in enumerate(hits, 1):
18. print(f"{i}. [{hit['_score']:.3f}] {hit['_source']['title']}")
`AI写代码
`
1. query = "why does adamw train transformers better than plain adam"
2. baseline_hits = search(query)
3. print(f"Query: {query}\n")
4. print("Top 5 (raw query):")
5. print_hits(baseline_hits)
`AI写代码
结果:
`
1. Top 5 (raw query):
2. 1. [0.792] Understanding AdamW through Proximal Methods and Scale-Freeness
3. 2. [0.760] Maximizing Communication Efficiency for Large-scale Training via 0/1 Adam
4. 3. [0.741] VectorAdam for Rotation Equivariant Geometry Optimization
5. 4. [0.731] Fast Adversarial Training with Adaptive Step Size
6. 5. [0.727] Adaptive Divergence for Rapid Adversarial Optimization
`AI写代码
顶部结果是相关的,但第 3–5 个结果偏向“对抗训练”和“旋转等变几何”,这两个方向都与主题无关。基准查询过短,无法将嵌入定位在正确的邻近区域。
使用 Inference API 生成假设文档
现在我们让 LLM 编写一个与查询完全匹配的假设摘要。该提示要求使用正式的学术语域,生成 150–200 个单词,并匹配真实 arXiv 摘要的密度:
`
1. def generate_hypothetical_abstract(query):
2. prompt = (
3. "You are helping improve search over a corpus of machine learning "
4. "paper abstracts from arXiv.\n\n"
5. "Given a short user query, write ONE plausible research paper abstract "
6. "(150-200 words) that would be a perfect match for that query. Use the "
7. "formal register and density of a real arXiv abstract: methods, setup, "
8. "findings. Do not add a title, headings, or any explanation. Return "
9. "only the abstract text itself.\n\n"
10. f"User query: {query}\n\nHypothetical abstract:"
11. )
13. response = es_client.inference.completion(
14. inference_id=HYDE_INFERENCE_ID,
15. input=prompt,
16. )
17. return response["completion"][0]["result"].strip()
`AI写代码
`
1. hypothetical = generate_hypothetical_abstract(query)
2. print(hypothetical)
`AI写代码
`
1. In this paper, we investigate the training dynamics of Transformer
2. models utilizing the Adam and AdamW optimization algorithms. While
3. Adam has been widely adopted for training deep learning models due
4. to its adaptive learning rates and momentum, we demonstrate that
5. the integration of weight decay in the AdamW variant substantially
6. improves the generalization capabilities of Transformers. Our
7. experimental setup encompasses a series of benchmark tasks, including
8. language modeling and text classification, where we train various
9. Transformer architectures (BERT, GPT-2, and T5) with both optimizers.
10. Through a comprehensive series of ablation studies, we reveal that
11. AdamW effectively decouples weight decay from the gradient updates,
12. leading to more stable learning dynamics and reduced overfitting.
13. Furthermore, we analyze the impact of hyperparameter tuning on
14. convergence rates and model performance, showing that AdamW
15. consistently outperforms Adam, particularly in scenarios with limited
16. training data.
`AI写代码
这个假设摘要包含了大量 transformer 特定的优化词汇:权重衰减、梯度更新、收敛、泛化、消融研究。这些词汇会将嵌入拉向正确的邻近区域。
HyDE 搜索
现在我们使用假设文档而不是原始查询进行搜索:
`
1. hyde_hits = search(hypothetical)
2. print("Top 5 (HyDE):")
3. print_hits(hyde_hits)
`AI写代码
`
1. Top 5 (HyDE):
2. 1. [0.839] Understanding AdamW through Proximal Methods and Scale-Freeness
3. 2. [0.761] Optimizing the optimizer for data driven deep neural networks
4. 3. [0.759] Trainable Weight Averaging for Fast Convergence and Better Generalization
5. 4. [0.757] Subformer: Exploring Weight Sharing for Parameter Efficiency in
6. Generative Transformers
7. 5. [0.756] 8-bit Optimizers via Block-wise Quantization
`AI写代码
结果发生了变化。“VectorAdam” 和 “对抗训练” 论文消失了,取而代之的是关于用于收敛的权重平均、transformer 中的参数效率以及优化器量化的论文,这些论文都更接近原始查询真正想要了解的内容。
HyDE 是否能提升语义搜索?评估结果
HyDE 在三个测试查询中的两个查询上将精确率和召回率提高了 50%。第三个查询已经明确指出了两个优化器,因此基准嵌入本身已经具有良好的定位。
我们为三个查询定义了一个人工整理的判断集合,其中列出了被认为相关的论文,并测量标准检索指标:
-
Precision@5: 前 5 个结果中属于相关性集合的比例。
-
Recall@5: 前 5 个结果中覆盖相关性集合的比例。
-
MRR: 第一个相关结果的倒数排名(如果没有相关结果,则为 0)。
`
1. RELEVANT_DOCS = {
2. "why does adamw train transformers better than plain adam": {
3. "Understanding AdamW through Proximal Methods and Scale-Freeness",
4. "Maximizing Communication Efficiency for Large-scale Training via 0/1 Adam",
5. "Subformer: Exploring Weight Sharing for Parameter Efficiency in Generative Transformers",
6. "Train Large, Then Compress: Rethinking Model Size for Efficient Training and Inference of Transformers",
7. "Trainable Weight Averaging for Fast Convergence and Better Generalization",
8. },
9. "is mixture of experts worth it for small language models": {
10. "Task-Specific Expert Pruning for Sparse Mixture-of-Experts",
11. "Exploring Extreme Parameter Compression for Pre-trained Language Models",
12. "Train Large, Then Compress: Rethinking Model Size for Efficient Training and Inference of Transformers",
13. "Balancing Expert Utilization in Mixture-of-Experts Layers Embedded in CNNs",
14. "Deep Ensembles on a Fixed Memory Budget: One Wide Network or Several Thinner Ones?",
15. "Learning Factored Representations in a Deep Mixture of Experts",
16. },
17. "how do you stop catastrophic forgetting during fine-tuning": {
18. "Understanding the Role of Training Regimes in Continual Learning",
19. "SupportNet: solving catastrophic forgetting in class incremental learning with support data",
20. "Explain to Not Forget: Defending Against Catastrophic Forgetting with XAI",
21. "Few-shot Continual Learning: a Brain-inspired Approach",
22. "Online Continual Learning under Extreme Memory Constraints",
23. "Continual Learning in Deep Neural Network by Using a Kalman Optimiser",
24. "On Tiny Episodic Memories in Continual Learning",
25. },
26. }
`AI写代码
`
1. import re
3. def normalize(title):
4. return re.sub(r"\s+", " ", title).strip()
6. def metrics(hits, relevant, k=5):
7. relevant_norm = {normalize(t) for t in relevant}
8. titles = [normalize(h["_source"]["title"]) for h in hits[:k]]
9. hits_in_top = sum(1 for t in titles if t in relevant_norm)
10. precision = hits_in_top / k
11. recall = hits_in_top / len(relevant_norm) if relevant_norm else 0.0
12. mrr = 0.0
13. for rank, t in enumerate(titles, start=1):
14. if t in relevant_norm:
15. mrr = 1 / rank
16. break
18. return {"precision@5": precision, "recall@5": recall, "mrr": mrr}
`AI写代码
在全部三个查询上运行两种方法,并收集结果:
`
1. results = []
2. for query, relevant in RELEVANT_DOCS.items():
3. baseline_hits = search(query)
4. hyde_hits = search(generate_hypothetical_abstract(query))
5. results.append({
6. "query": query,
7. "relevant": relevant,
8. "baseline_hits": baseline_hits,
9. "hyde_hits": hyde_hits,
10. })
`AI写代码
结果:
HyDE 提升了查询 2 和查询 3 的精确率和召回率,但在查询 1 上与基准方法持平。查询 1(“为什么 adamw 比普通 adam 更适合训练 transformers”)已经明确指出了两个优化器,因此基准嵌入无需额外帮助就能接近正确的论文。假设摘要增加了信息密度,但同时也采用了特定的表述方式,这可能会用一篇相关论文替换另一篇相关论文,而不会带来净收益。查询 2 和查询 3 更加模糊,基准方法缺少足够的信息进行定位。在这些情况下,假设文档通过补充短查询中缺少的领域词汇填补了差距,将嵌入拉入向量空间中更加精确的区域。
何时使用 HyDE(以及何时不使用)
HyDE 并不是一种通用的升级方案。以下是它适用的场景以及需要谨慎使用的情况:
适合使用 HyDE 的场景:
-
针对正式文档语料库(学术论文、法律文件、技术报告)的简短、随意查询。
-
领域特定的语料库,其中用户提问方式和文档写作方式之间存在较大的语域差距。
-
检索流程中精确率或召回率不够理想,希望在无需重新建立索引或更改嵌入模型的情况下,以低成本技术改善结果。
实际考虑因素:
-
使用小型、快速的模型,因为假设文档只需要在主题上正确,而不需要在事实层面完全准确。
-
为重复或相似的查询模式缓存假设文档,以避免重复的 LLM 调用。
-
考虑有选择地运行 HyDE:对于较短的查询(少于约 10 个单词)使用它;对于已经包含足够语义信号的较长、更具体查询,则跳过它。
结论
HyDE 通过使用由 LLM 生成的假设文档作为搜索向量,缩小了简短查询和正式文档之间的嵌入分布差距。Elasticsearch Inference API 负责处理 LLM 生成和嵌入步骤,而无需离开集群,使实现过程更加简洁。
在我们的数据集中,HyDE 在三个测试查询中的两个查询上将精确率和召回率提高了 50%,并在第三个查询上保持持平。不过,它并不是免费的:它会为每个查询增加一次 LLM 往返调用,而且当模型对一个模糊问题采用某一种解释时,它可能会缩小检索范围,而不是扩大检索范围。它是一种值得关注的替代方案,但在将其作为默认方案采用之前,应首先在你自己的数据上进行评估。
后续步骤和进一步阅读
这篇内容对你有多大帮助?
原文:HyDE in Elasticsearch: 50% better semantic search precision - Elasticsearch Labs