本記事は Precise Zero-Shot Dense Retrieval without Relevance Labels(Gao et al., 2022)の解説記事です。
論文概要(Abstract)
密検索(Dense Retrieval)はさまざまなタスクで有効性が示されているが、関連度ラベルが利用できないゼロショット環境での構築は依然として困難である。著者らは、クエリから直接関連度を学習する代わりに、Hypothetical Document Embeddings(HyDE)を提案している。HyDEでは、まずInstruction-following LLM(InstructGPT等)にクエリに対する仮説的な文書を生成させ、その文書を教師なし対照学習エンコーダ(Contriever等)でベクトル化して検索に利用する。著者らの実験では、HyDEがゼロショット密検索において当時のSOTAであるContrieverを大幅に上回り、多言語タスクでも強い性能を示したと報告されている。
この記事は Zenn記事: セマンティック検索の本番精度チューニング:クエリ最適化×多段リランキング×評価ループ実践 の深掘りです。
情報源
- arXiv ID: 2212.10496
- URL: https://arxiv.org/abs/2212.10496
- 著者: Luyu Gao (Carnegie Mellon University), Xueguang Ma, Jimmy Lin (University of Waterloo), Jamie Callan (Carnegie Mellon University)
- 発表年: 2022年
- 分野: cs.IR, cs.CL
- 公式実装: https://github.com/texttron/hyde
背景と動機(Background & Motivation)
密検索(Dense Retrieval)は、クエリと文書をそれぞれ密ベクトルに変換し、ベクトル空間での近傍探索によって関連文書を取得する手法である。DPR(Karpukhin et al., 2020)やContriever(Izacard et al., 2022)に代表されるように、BM25等の語彙ベース手法を上回る性能が報告されてきた。
しかし、高精度な密検索を実現するには大量の関連度ラベル付きデータ(クエリ-文書ペア)が必要であり、新しいドメインへの適用や多言語展開では事前にラベルを用意することが困難である。教師なしの密検索手法(Contriever等)も存在するが、ゼロショット設定ではBM25に及ばないケースが多かった。
著者らは、この問題の根本がクエリと文書の非対称性(query-document asymmetry)にあると指摘している。クエリは短くキーワード的であるのに対し、文書は長く詳細な記述を含む。この形式の違いにより、同じベクトル空間で両者をマッチングすることが難しくなる。HyDEはこの非対称性をLLMによる仮説文書生成で解消することを目指している。
主要な貢献(Key Contributions)
- 貢献1: LLMで仮説文書を生成し、文書同士のベクトル比較に変換するHyDE手法を提案。ゼロショット設定で関連度ラベルを一切使わずに密検索を実現した
- 貢献2: TREC DL、BEIR、FEVER、MIRACL等の多様なベンチマークで、教師なしSOTA(Contriever)を大幅に上回る性能を達成したと報告している
- 貢献3: 英語で仮説文書を生成し多言語コーパスを検索するクロスリンガル設定でも有効であることを示した(MIRACL 18言語で検証)
技術的詳細(Technical Details)
アーキテクチャ/手法
HyDEは2つのコンポーネントで構成される。
graph LR
A[ユーザークエリ q] --> B[Instruction-following LLM]
B --> C[仮説文書 d̂]
C --> D[教師なしエンコーダ f]
D --> E["検索ベクトル f(d̂)"]
E --> F[コーパスインデックスとの近傍探索]
F --> G[検索結果]
Stage 1: 仮説文書の生成
Instruction-following LLM(論文ではInstructGPT / text-davinci-003を使用)に対し、クエリに回答する仮説的な文書を生成させる。この文書は事実として正確である必要はなく、関連性のパターン(relevance patterns)を捉えていればよい。タスクごとにプロンプトテンプレートが異なる。
| タスク | プロンプト方針 |
|---|---|
| Web Search | “Please write a passage to answer the question” |
| Open-domain QA | “Please write a short passage to answer the question” |
| Fact Verification | “Please write a true/false statement supporting/refuting the claim” |
| 多言語検索 | 英語で仮説文書を生成し、クロスリンガル検索に利用 |
Stage 2: エンコードと検索
生成された仮説文書を教師なし対照学習エンコーダ(Contriever)でベクトル化し、同じエンコーダで事前構築されたコーパスインデックスに対して近傍探索を実行する。
数学的定式化
理想的な密検索では、クエリ $q$ に関連する文書集合の期待値ベクトルが最適な検索ベクトルとなる。
\[\mathbf{v}^* = \mathbb{E}_{d \sim p(d|q)}[f(d)]\]ここで、
- $p(d \mid q)$: クエリ $q$ が与えられたときの関連文書の分布
- $f(d)$: 文書 $d$ のエンコーダ出力ベクトル
HyDEでは、LLMの生成分布 $p_{\text{LLM}}(d \mid q)$ でこの $p(d \mid q)$ を近似する。
\[p_{\text{LLM}}(d|q) \approx p(d|q)\]実用上は1つの仮説文書 $\hat{d}$ をサンプリングしてMonte Carlo近似を行う。
\[\mathbf{v} \approx f(\hat{d}), \quad \hat{d} \sim p_{\text{LLM}}(d|q)\]検索スコアはコサイン類似度で計算する。
\[\text{sim}(q, d_i) = \frac{f(\hat{d})^{\top} \cdot f(d_i)}{\|f(\hat{d})\| \cdot \|f(d_i)\|}\]この定式化のポイントは、クエリエンコーダを使わず、文書エンコーダのみで検索が完結する点にある。仮説文書と実文書はどちらも同じエンコーダで同じ空間にマッピングされるため、クエリ-文書の非対称性が解消される。
アルゴリズム
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
from sentence_transformers import SentenceTransformer
from openai import OpenAI
import numpy as np
client = OpenAI()
encoder = SentenceTransformer("facebook/contriever-msmarco")
def hyde_search(
query: str,
corpus_embeddings: np.ndarray,
documents: list[str],
top_k: int = 10,
) -> list[tuple[str, float]]:
"""HyDEによるゼロショット密検索を実行する。
Args:
query: ユーザークエリ
corpus_embeddings: コーパス文書のEmbedding行列
documents: コーパス文書のリスト
top_k: 返す結果数
Returns:
(文書, スコア) のリスト
"""
# Stage 1: 仮説文書の生成
prompt = f"""Please write a passage to answer the question.
Question: {query}
Passage:"""
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": prompt}],
temperature=0.0,
max_tokens=256,
)
hypothetical_doc = response.choices[0].message.content.strip()
# Stage 2: 仮説文書をエンコードして検索
hyde_embedding = encoder.encode(hypothetical_doc, normalize_embeddings=True)
scores = np.dot(corpus_embeddings, hyde_embedding)
top_indices = np.argsort(scores)[::-1][:top_k]
return [(documents[i], float(scores[i])) for i in top_indices]
実装のポイント(Implementation)
著者らの論文および公式実装から読み取れる実装上の注意点を整理する。
プロンプト設計がタスク性能を左右する: タスクごとに適切なプロンプトを設計する必要がある。Web検索向けに「passage」を求めるプロンプトとQA向けに「short passage」を求めるプロンプトでは、生成される仮説文書の粒度が異なり検索性能に影響する。
エンコーダの選択: 論文ではContrieverを使用しているが、intfloat/multilingual-e5-large等の後発エンコーダでも同様のアプローチが適用可能である。重要なのは、コーパスインデックスの構築に使用したエンコーダと同一のエンコーダで仮説文書をエンコードすることである。
サンプリング数のトレードオフ: 論文では仮説文書を複数生成してアンサンブルする実験も行っているが、1サンプルでもほぼ同等の性能が得られたと報告されている。レイテンシ・コストの観点から、本番環境では1サンプルが現実的である。
条件付き適用: Zenn記事で紹介されている条件付きHyDEフォールバックは、この制約を実運用で解決するアプローチである。通常検索の信頼度が低い場合のみHyDEを適用することで、不要なLLM呼び出しを削減しつつ、検索精度の改善を狙える。
Production Deployment Guide
AWS実装パターン(コスト最適化重視)
HyDEパイプラインは「LLM呼び出し → エンコード → ベクトル検索」の3段階であり、LLM呼び出しのコスト管理が最重要となる。
| 規模 | 月間リクエスト | 推奨構成 | 月額コスト | 主要サービス |
|---|---|---|---|---|
| Small | ~3,000 (100/日) | Serverless | $80-200 | Lambda + Bedrock + OpenSearch Serverless |
| Medium | ~30,000 (1,000/日) | Hybrid | $400-1,000 | Lambda + ECS Fargate + ElastiCache + OpenSearch |
| Large | 300,000+ (10,000/日) | Container | $2,500-6,000 | EKS + Karpenter + OpenSearch + Bedrock Batch |
Small構成の詳細(月額$80-200):
- Lambda: 仮説文書生成+エンコード、1GB RAM、60秒タイムアウト($25/月)
- Bedrock: Claude 3.5 Haiku(仮説文書生成)、Prompt Caching有効($100/月)
- OpenSearch Serverless: ベクトルインデックス($25/月)
- DynamoDB: 仮説文書キャッシュ、On-Demand($10/月)
- CloudWatch: 基本監視($5/月)
コスト削減テクニック:
- 同一クエリの仮説文書をDynamoDBにキャッシュして重複LLM呼び出しを防止
- Bedrock Prompt Cachingでシステムプロンプト部分のコストを30-90%削減
- Bedrock Batch APIで非リアルタイム処理のコストを50%削減
- OpenSearch Serverlessのアイドル時自動スケールダウン
コスト試算の注意事項: 上記は2026年3月時点のAWS ap-northeast-1(東京)リージョン料金に基づく概算値です。実際のコストはトラフィックパターン、生成文書の平均トークン数、キャッシュヒット率により変動します。最新料金は AWS料金計算ツール で確認してください。
Terraformインフラコード
Small構成(Serverless): Lambda + Bedrock + OpenSearch Serverless
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# --- IAMロール(最小権限) ---
resource "aws_iam_role" "lambda_hyde" {
name = "lambda-hyde-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = { Service = "lambda.amazonaws.com" }
}]
})
}
resource "aws_iam_role_policy" "bedrock_invoke" {
role = aws_iam_role.lambda_hyde.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Action = ["bedrock:InvokeModel", "bedrock:InvokeModelWithResponseStream"]
Resource = "arn:aws:bedrock:ap-northeast-1::foundation-model/anthropic.claude-3-5-haiku*"
}]
})
}
# --- Lambda関数(HyDE処理) ---
resource "aws_lambda_function" "hyde_handler" {
filename = "hyde_lambda.zip"
function_name = "hyde-search-handler"
role = aws_iam_role.lambda_hyde.arn
handler = "index.handler"
runtime = "python3.12"
timeout = 60
memory_size = 1024
environment {
variables = {
BEDROCK_MODEL_ID = "anthropic.claude-3-5-haiku-20241022-v1:0"
OPENSEARCH_ENDPOINT = aws_opensearchserverless_collection.hyde.collection_endpoint
DYNAMODB_TABLE = aws_dynamodb_table.hyde_cache.name
ENCODER_MODEL = "intfloat/multilingual-e5-large-instruct"
}
}
}
# --- DynamoDB(仮説文書キャッシュ) ---
resource "aws_dynamodb_table" "hyde_cache" {
name = "hyde-doc-cache"
billing_mode = "PAY_PER_REQUEST"
hash_key = "query_hash"
attribute {
name = "query_hash"
type = "S"
}
ttl {
attribute_name = "expire_at"
enabled = true
}
}
# --- CloudWatch アラーム ---
resource "aws_cloudwatch_metric_alarm" "hyde_latency" {
alarm_name = "hyde-latency-high"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = 2
metric_name = "Duration"
namespace = "AWS/Lambda"
period = 300
statistic = "p95"
threshold = 30000
alarm_description = "HyDE Lambda P95レイテンシが30秒を超過"
dimensions = {
FunctionName = aws_lambda_function.hyde_handler.function_name
}
}
Large構成(Container): EKS + OpenSearch
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
module "eks" {
source = "terraform-aws-modules/eks/aws"
version = "~> 20.0"
cluster_name = "hyde-search-cluster"
cluster_version = "1.31"
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnets
cluster_endpoint_public_access = true
enable_cluster_creator_admin_permissions = true
}
resource "kubectl_manifest" "karpenter_provisioner" {
yaml_body = <<-YAML
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: hyde-nodepool
spec:
template:
spec:
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: ["spot"]
- key: node.kubernetes.io/instance-type
operator: In
values: ["m7i.xlarge", "m7i.2xlarge"]
limits:
cpu: "64"
memory: "256Gi"
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
consolidateAfter: 30s
YAML
}
resource "aws_budgets_budget" "hyde_monthly" {
name = "hyde-monthly-budget"
budget_type = "COST"
limit_amount = "6000"
limit_unit = "USD"
time_unit = "MONTHLY"
notification {
comparison_operator = "GREATER_THAN"
threshold = 80
threshold_type = "PERCENTAGE"
notification_type = "ACTUAL"
subscriber_email_addresses = ["ops@example.com"]
}
}
セキュリティベストプラクティス
- ネットワーク: OpenSearch ServerlessへのアクセスはVPCエンドポイント経由に限定
- 認証: IAMロール最小権限、Bedrockモデル呼び出し権限はモデル単位で制限
- シークレット: APIキー等はSecrets Manager使用、環境変数ハードコード禁止
- 暗号化: OpenSearchインデックス・DynamoDB・S3すべてKMS暗号化
- 監査: CloudTrail全リージョン有効化
運用・監視設定
1
2
3
4
-- CloudWatch Logs Insights: HyDE処理のレイテンシ分析
fields @timestamp, query, hyde_latency_ms, search_latency_ms, total_latency_ms
| stats avg(hyde_latency_ms) as avg_hyde, pct(total_latency_ms, 95) as p95_total by bin(5m)
| filter total_latency_ms > 5000
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import boto3
cloudwatch = boto3.client('cloudwatch')
# Bedrockトークン使用量アラート
cloudwatch.put_metric_alarm(
AlarmName='hyde-bedrock-token-spike',
ComparisonOperator='GreaterThanThreshold',
EvaluationPeriods=1,
MetricName='TokenUsage',
Namespace='Custom/HyDE',
Period=3600,
Statistic='Sum',
Threshold=200000,
AlarmDescription='HyDE仮説文書生成のトークン使用量異常'
)
コスト最適化チェックリスト
- ~100 req/日 → Lambda + Bedrock (Serverless) - $80-200/月
- ~1,000 req/日 → ECS Fargate + OpenSearch (Hybrid) - $400-1,000/月
- 10,000+ req/日 → EKS + Spot (Container) - $2,500-6,000/月
- 仮説文書キャッシュ(DynamoDB TTL付き)でLLM呼び出し削減
- Bedrock Prompt Cachingでシステムプロンプトコスト削減(30-90%)
- Bedrock Batch API活用(非リアルタイム処理で50%削減)
- 条件付きHyDE(信頼度低い場合のみLLM呼び出し)でコスト最適化
- Spot Instances使用(EKS構成で最大90%削減)
- OpenSearch Serverlessのアイドル時スケールダウン
- Lambda メモリサイズ最適化(CloudWatch Insights分析)
- AWS Budgets設定(月額予算の80%で警告)
- CloudWatch アラーム(レイテンシ・トークン使用量)
- Cost Anomaly Detection有効化
- タグ戦略(環境別・プロジェクト別でコスト可視化)
- DynamoDBキャッシュTTL(7日推奨)で古いエントリ自動削除
- エンコーダモデルのバッチ推論(複数クエリを一括処理)
- Reserved Instances(OpenSearch、1年コミットで最大72%削減)
- CloudTrail/Config有効化(セキュリティ監査)
- KMS暗号化(OpenSearch/DynamoDB/S3)
- 日次コストレポート(SNS/Slack通知)
実験結果(Results)
Web Search(TREC DL 2019 & 2020)
論文Table 1より、nDCG@10の結果を示す。
| System | DL19 nDCG@10 | DL20 nDCG@10 |
|---|---|---|
| BM25(ベースライン) | 50.6 | 47.9 |
| Contriever(教師なし) | 44.5 | 44.6 |
| HyDE(Contriever + InstructGPT) | 56.6 | 61.7 |
| DPR(NQでfine-tuned) | 62.2 | 59.2 |
| Contriever-MSMARCO(fine-tuned) | 71.8 | 68.3 |
著者らの報告によると、HyDEはゼロショット設定でBM25を+6.0〜+13.8ポイント、Contrieverを+12.1〜+17.1ポイント上回っている。ただし、MSMARCOでfine-tunedされたContrieverには及ばない。
多言語検索(MIRACL)
論文Table 3より、MIRACL 18言語での平均nDCG@10を示す。
| Setting | nDCG@10 avg |
|---|---|
| mContriever(教師なし) | ~25 |
| HyDE + mContriever | ~42 |
著者らによれば、英語で仮説文書を生成し多言語コーパスを検索するクロスリンガル設定でも全言語で改善が確認されている。
Fact Verification(FEVER)
| System | Recall@100 |
|---|---|
| Contriever | 84.2 |
| HyDE | 88.6 |
性能が低下するケース
著者らはArguAnaデータセット(議論的文書の検索)ではHyDEの性能が低下することを報告している。LLMが事実的な文書を生成する傾向があるため、反論や議論的な文書を検索するタスクではミスマッチが発生する。
実運用への応用(Practical Applications)
HyDEはZenn記事で紹介されている「条件付きHyDEフォールバック」パターンの理論的基盤となる手法である。実運用での適用場面として以下が考えられる。
新規ドメインへの展開: ラベル付きデータがない新しい分野(社内ナレッジベース、法律文書等)にセマンティック検索を導入する際、ゼロショットで高精度な検索を実現できる。
多言語サポート: 英語のLLMで仮説文書を生成し、多言語コーパスを検索するクロスリンガルアプローチは、多言語対応のRAGシステム構築に直接適用できる。
レイテンシとコストの考慮: 本番環境ではLLM呼び出しのレイテンシ(100-300ms)とAPIコストが課題となる。Zenn記事で紹介されている条件付きフォールバック(通常検索の信頼度が低い場合のみHyDEを適用)は、この課題に対する実用的な解決策である。
関連研究(Related Work)
- DPR(Karpukhin et al., 2020): 教師あり密検索の基盤手法。質問-パッセージペアで対照学習を行うが、大量のラベル付きデータが必要。HyDEはラベル不要でこれに匹敵する性能を目指している
- Contriever(Izacard et al., 2022): 教師なし対照学習による密検索手法。HyDEのエンコーダとして使用されており、HyDEはContriever単体の性能を大幅に上回ることが示されている
- Query Expansion(Rocchio, 1971; PRF): 古典的なクエリ拡張手法との対比。HyDEはクエリ拡張の一種とも捉えられるが、トークンレベルではなくベクトル空間での拡張を行う点が異なる
- GPL(Wang et al., 2022): 生成モデルで擬似ラベルを生成し密検索モデルをfine-tuneする手法。HyDEはfine-tuneを不要とする点で異なるアプローチを取っている
まとめと今後の展望
HyDEは「LLMで仮説文書を生成し、文書-文書のベクトル比較に変換する」というシンプルな発想で、ゼロショット密検索の精度を大幅に改善した手法である。著者らの実験では、教師なしContrieverに対しnDCG@10で+12〜17ポイントの改善が報告されている。
実務への示唆として、ラベル付きデータの調達が困難なドメインでのセマンティック検索導入において有効であり、Zenn記事で紹介されている条件付きフォールバック戦略と組み合わせることで、コストとレイテンシを抑えた本番運用が可能になる。今後は、より小型のLLM(ローカル推論対応モデル)での仮説文書生成や、ドメイン特化プロンプトの体系的な設計が研究課題として残されている。
参考文献
- arXiv: https://arxiv.org/abs/2212.10496
- Code: https://github.com/texttron/hyde
- Related Zenn article: https://zenn.dev/0h_n0/articles/42ecab7378cf0b
:::message この記事はAI(Claude Code)により自動生成されました。内容は論文の解説であり、筆者自身が実験を行ったものではありません。実際の利用時は原論文および公式実装もご確認ください。 :::