概要
HyPA-RAG(Hybrid Parameter-Adaptive RAG)は、AI法律・政策ドメインに特化したRAGシステムです。arXiv論文2409.09046およびNAACL 2025 Industry Trackで発表され、ハイブリッド検索(BM25 + Dense + Knowledge Graph)とクエリ複雑度に応じたパラメータ適応により、法律文書検索の精度と文脈適合性を大幅に改善しています。
本記事では、HyPA-RAGの技術的詳細、ハイブリッド検索アーキテクチャ、パラメータ適応メカニズム、NYC Local Law 144(LL144)での評価結果を、修士学生レベルの読者向けに深掘り解説します。
背景:法律ドメインRAGの課題
大規模言語モデル(LLM)は、法律・政策分野において以下の課題を抱えています。
- 知識の陳腐化: 訓練データカットオフ後の法改正に対応できない
- ハルシネーション: 存在しない判例や条文を生成
- 複雑な推論の弱さ: 多段階の法的推論が不正確
従来のRAGシステムも、以下の問題があります。
- 検索エラー: 関連文書を見逃す、無関連文書を取得
- コンテキスト統合の失敗: 長文書を適切にLLMに入力できない
- 高コスト: 過剰な検索とトークン消費
HyPA-RAGは、これらの課題をハイブリッド検索と適応的パラメータチューニングで解決します。
HyPA-RAGのアーキテクチャ
全体構成
HyPA-RAGは以下の4つのコンポーネントから構成されます。
1
クエリ複雑度分類器 → ハイブリッド検索 → コンテキスト統合 → LLM生成
1. クエリ複雑度分類器
クエリの複雑度を分類し、検索パラメータを動的調整します。
複雑度のレベル:
- Low: 単純なファクト検索(例: “LL144の施行日は?”)
- Medium: 中程度の推論(例: “LL144は誰に適用されるか?”)
- High: 複雑な多段階推論(例: “LL144違反の場合の法的責任は?”)
分類器の実装:
教師あり学習(BERT系モデル)でクエリテキストから複雑度を予測します。
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
from transformers import AutoTokenizer, AutoModelForSequenceClassification
import torch
# クエリ複雑度分類器
class QueryComplexityClassifier:
def __init__(self, model_name="bert-base-uncased"):
self.tokenizer = AutoTokenizer.from_pretrained(model_name)
self.model = AutoModelForSequenceClassification.from_pretrained(
model_name,
num_labels=3 # Low, Medium, High
)
def classify(self, query: str) -> str:
"""クエリ複雑度を分類
Returns:
"low", "medium", "high"
"""
inputs = self.tokenizer(
query,
return_tensors="pt",
truncation=True,
max_length=512
)
with torch.no_grad():
outputs = self.model(**inputs)
logits = outputs.logits
predicted_class = torch.argmax(logits, dim=1).item()
return ["low", "medium", "high"][predicted_class]
# 使用例
classifier = QueryComplexityClassifier()
query = "What are the penalties for violating LL144?"
complexity = classifier.classify(query)
print(f"Query complexity: {complexity}")
# Output: Query complexity: high
2. ハイブリッド検索
HyPA-RAGは、Sparse(BM25)、Dense(ベクトル検索)、Knowledge Graphの3つの検索手法を組み合わせます。
2.1 Sparse検索(BM25)
BM25アルゴリズム:
クエリ \(Q = \{q_1, q_2, \ldots, q_n\}\) と文書 \(D\) のスコアは以下で計算されます。
\[\text{BM25}(Q, D) = \sum_{i=1}^{n} \text{IDF}(q_i) \cdot \frac{f(q_i, D) \cdot (k_1 + 1)}{f(q_i, D) + k_1 \cdot (1 - b + b \cdot \frac{|D|}{\text{avgdl}})}\]ここで、
- \(f(q_i, D)\): 文書 \(D\) 内の単語 \(q_i\) の出現頻度
$$ D $$: 文書長 - \(\text{avgdl}\): コーパスの平均文書長
- \(k_1, b\): チューニングパラメータ(通常 \(k_1=1.5\), \(b=0.75\))
実装:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from rank_bm25 import BM25Okapi
# コーパス準備
corpus = [
"LL144 requires annual bias audits of AEDT systems",
"Penalties for LL144 violations include fines up to $1,500",
# ...
]
tokenized_corpus = [doc.split() for doc in corpus]
# BM25インデックス
bm25 = BM25Okapi(tokenized_corpus)
# 検索
query = "What are the audit requirements?"
tokenized_query = query.split()
bm25_scores = bm25.get_scores(tokenized_query)
# Top-k取得
k = 5
top_k_indices = bm25_scores.argsort()[-k:][::-1]
bm25_docs = [corpus[i] for i in top_k_indices]
2.2 Dense検索(ベクトル検索)
埋め込みモデル:
Sentence-BERTやOpenAI Embeddingsで文書とクエリをベクトル化し、コサイン類似度で検索します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from sentence_transformers import SentenceTransformer
import numpy as np
# 埋め込みモデル
embedder = SentenceTransformer('all-MiniLM-L6-v2')
# 文書埋め込み
doc_embeddings = embedder.encode(corpus)
# クエリ埋め込み
query_embedding = embedder.encode(query)
# コサイン類似度
similarities = np.dot(doc_embeddings, query_embedding) / (
np.linalg.norm(doc_embeddings, axis=1) * np.linalg.norm(query_embedding)
)
# Top-k取得
top_k_indices = similarities.argsort()[-k:][::-1]
dense_docs = [corpus[i] for i in top_k_indices]
2.3 Knowledge Graph検索
Knowledge Graphの構築:
法律文書から実体(Entity)と関係(Relation)を抽出し、トリプレット \((h, r, t)\) のグラフを構築します。
1
2
3
(LL144, regulates, AEDT systems)
(AEDT, used_for, hiring decisions)
(bias audit, required_by, LL144)
トリプレット取得:
クエリから関連するトリプレットを取得し、文脈として利用します。
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
import networkx as nx
# Knowledge Graph(簡略版)
kg = nx.DiGraph()
kg.add_edges_from([
("LL144", "AEDT systems", {"relation": "regulates"}),
("AEDT", "hiring decisions", {"relation": "used_for"}),
("bias audit", "LL144", {"relation": "required_by"}),
])
def retrieve_triplets(query: str, kg: nx.DiGraph, depth: int = 2) -> list:
"""クエリから関連トリプレットを取得
Args:
query: ユーザークエリ
kg: Knowledge Graph
depth: 探索深度(複雑度に応じて調整)
Returns:
関連トリプレットリスト
"""
# クエリからキーワード抽出(簡略化)
keywords = extract_keywords(query) # 例: ["LL144", "audit"]
triplets = []
for kw in keywords:
if kw in kg.nodes:
# depth-hop以内のトリプレットを取得
for neighbor in nx.single_source_shortest_path_length(kg, kw, cutoff=depth):
if kg.has_edge(kw, neighbor):
relation = kg[kw][neighbor]["relation"]
triplets.append((kw, relation, neighbor))
return triplets
# 使用例
triplets = retrieve_triplets("LL144 audit requirements", kg, depth=2)
print(triplets)
# Output: [('LL144', 'regulates', 'AEDT systems'), ('bias audit', 'required_by', 'LL144')]
3. パラメータ適応メカニズム
クエリ複雑度に応じて、検索パラメータを動的に調整します。
適応パラメータ:
| 複雑度 | BM25 Top-k | Dense Top-k | KG Depth | 統合重み(BM25:Dense:KG) |
|---|---|---|---|---|
| Low | 3 | 3 | 1 | 0.5:0.4:0.1 |
| Medium | 5 | 5 | 2 | 0.4:0.4:0.2 |
| High | 10 | 10 | 3 | 0.3:0.4:0.3 |
実装:
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
class AdaptiveHybridRetriever:
def __init__(self, bm25, embedder, kg):
self.bm25 = bm25
self.embedder = embedder
self.kg = kg
self.classifier = QueryComplexityClassifier()
# 複雑度別パラメータ
self.params = {
"low": {"bm25_k": 3, "dense_k": 3, "kg_depth": 1, "weights": (0.5, 0.4, 0.1)},
"medium": {"bm25_k": 5, "dense_k": 5, "kg_depth": 2, "weights": (0.4, 0.4, 0.2)},
"high": {"bm25_k": 10, "dense_k": 10, "kg_depth": 3, "weights": (0.3, 0.4, 0.3)},
}
def retrieve(self, query: str, corpus: list) -> list:
"""適応的ハイブリッド検索"""
# 複雑度分類
complexity = self.classifier.classify(query)
params = self.params[complexity]
# BM25検索
bm25_docs = self.bm25_search(query, corpus, k=params["bm25_k"])
# Dense検索
dense_docs = self.dense_search(query, corpus, k=params["dense_k"])
# KG検索
kg_triplets = retrieve_triplets(query, self.kg, depth=params["kg_depth"])
# スコア統合
combined_docs = self.combine_results(
bm25_docs,
dense_docs,
kg_triplets,
weights=params["weights"]
)
return combined_docs
def combine_results(self, bm25_docs, dense_docs, kg_triplets, weights):
"""スコア統合(正規化 + 重み付け和)"""
w_bm25, w_dense, w_kg = weights
# スコア正規化とマージ(詳細略)
# ...
return merged_docs
評価:NYC Local Law 144(LL144)
HyPA-RAGは、NYC Local Law 144(AI採用ツールの偏見監査を義務化する法律)を対象に評価されました。
評価指標
- Retrieval Accuracy: 関連文書の取得精度
- Response Fidelity: 生成回答の事実正確性
- Contextual Precision: 文脈の適合性
結果
| 手法 | Retrieval Accuracy | Response Fidelity | Contextual Precision |
|---|---|---|---|
| 標準RAG(Dense単独) | 72.3% | 68.5% | 65.2% |
| BM25 + Dense | 78.1% | 73.4% | 70.8% |
| HyPA-RAG(提案手法) | 85.7% | 81.2% | 78.9% |
改善率:
- Retrieval Accuracy: +13.4%
- Response Fidelity: +12.7%
- Contextual Precision: +13.7%
複雑度別の効果
| クエリ複雑度 | 標準RAG正解率 | HyPA-RAG正解率 | 改善率 |
|---|---|---|---|
| Low | 78.5% | 89.2% | +10.7% |
| Medium | 70.1% | 83.5% | +13.4% |
| High | 61.3% | 76.8% | +15.5% |
考察: 複雑なクエリほど、パラメータ適応とKnowledge Graphの効果が顕著です。
実装のポイント
1. Knowledge Graphの構築
法律文書からトリプレットを抽出する際は、Named Entity Recognition(NER)とRelation Extraction(RE)を組み合わせます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from transformers import pipeline
# NERモデル(法律ドメイン特化が理想)
ner = pipeline("ner", model="dbmdz/bert-large-cased-finetuned-conll03-english")
# Relation Extractionモデル
# 例: SpaCyのカスタムモデルや事前訓練済みREモデル
def build_knowledge_graph(documents: list) -> nx.DiGraph:
"""法律文書からKnowledge Graph構築"""
kg = nx.DiGraph()
for doc in documents:
# NERで実体抽出
entities = ner(doc)
# REで関係抽出(詳細略)
# ...
# トリプレット追加
# kg.add_edges_from(triplets)
return kg
2. クエリ複雑度分類の訓練データ作成
アノテーション戦略:
- 専門家(法律家)が100〜200クエリに複雑度ラベル付け
- Active Learningで効率的にデータ拡張
- GPT-4で擬似ラベル生成 → 人間検証
3. ハイブリッド検索のチューニング
重み \(w_{\text{BM25}}, w_{\text{Dense}}, w_{\text{KG}}\) は、開発セットでグリッドサーチまたはBayesian Optimizationで最適化します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from sklearn.model_selection import ParameterGrid
# グリッドサーチ
param_grid = {
"w_bm25": [0.3, 0.4, 0.5],
"w_dense": [0.3, 0.4, 0.5],
"w_kg": [0.1, 0.2, 0.3],
}
best_params = None
best_score = 0
for params in ParameterGrid(param_grid):
if sum(params.values()) != 1.0:
continue # 重みの合計は1
# 評価
score = evaluate_retrieval(params)
if score > best_score:
best_score = score
best_params = params
print(f"Best params: {best_params}, Score: {best_score}")
実運用への応用
適用が有効なドメイン
- 法律: 判例検索、契約書レビュー
- 医療: 診療ガイドライン検索、薬剤相互作用
- 金融: 規制コンプライアンス、リスク分析
コスト削減効果
適応的パラメータチューニングにより、以下のコストを削減できます。
- 検索コスト: 単純クエリでは少数のTop-k検索で十分
- LLMトークンコスト: 関連文書のみを入力し、無駄なトークン消費を削減
試算例:
- 標準RAG: 平均10文書 × 500トークン/文書 = 5,000トークン/クエリ
- HyPA-RAG: 平均6文書 × 500トークン/文書 = 3,000トークン/クエリ
- 削減率: 40%
関連研究
ハイブリッド検索の先行研究
- Hybrid Search in Elasticsearch: BM25 + kNN検索の組み合わせ
- ColBERT: 遅延相互作用モデルによる精度向上
- SPLADE: Sparse + Denseの統合表現学習
HyPA-RAGはこれらにKnowledge Graphとパラメータ適応を追加した点が新規性です。
RAGの自動最適化
- Self-RAG: 生成時に取得と生成を交互に実行し、自己評価で改善
- Adaptive RAG: クエリに応じて検索戦略を動的変更
HyPA-RAGは、クエリ複雑度という明示的な制御信号を使う点で異なります。
まとめ
HyPA-RAGは、ハイブリッド検索(BM25 + Dense + Knowledge Graph)とクエリ複雑度に応じたパラメータ適応により、法律ドメインRAGの精度と効率を大幅に改善しました。主な貢献は以下の通りです。
- 検索精度向上: 3つの検索手法の相補的活用で85.7%の精度達成
- 適応的最適化: 複雑なクエリほど大きな改善(+15.5%)
- コスト削減: トークン消費を40%削減
- ドメイン特化: Knowledge Graphによる法律知識の構造化
本手法は、法律以外のドメイン(医療、金融、科学)にも適用可能であり、RAGシステムの実運用における重要な指針となるでしょう。
参考文献
- Kalra, A., et al. (2024). HyPA-RAG: A Hybrid Parameter Adaptive Retrieval-Augmented Generation System for AI Legal and Policy Applications. arXiv:2409.09046. https://arxiv.org/abs/2409.09046
- Kalra, A., et al. (2025). HyPA-RAG. NAACL 2025 Industry Track.
- Robertson, S., & Zaragoza, H. (2009). The Probabilistic Relevance Framework: BM25 and Beyond. Foundations and Trends in Information Retrieval.
- Reimers, N., & Gurevych, I. (2019). Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks. EMNLP 2019.