🙄

Faiss、ElasticSearch、RAGの関係性:検索技術から生成AIへの架け橋

に公開

はじめに

最近、生成AIの話題で頻繁に耳にするようになった「RAG」(Retrieval-Augmented Generation)。このRAGを支える重要な技術として、FaissやElasticSearchといった検索エンジンが注目されています。しかし、これらの技術がどのように関連し、RAGシステムの中でどのような役割を果たしているのかは、意外と理解しづらいポイントです。

この記事では、Faiss、ElasticSearch、RAGの関係性について、基本から実装の詳細まで分かりやすく解説していきます。

RAGとは何か?

まずは基本から。RAG(Retrieval-Augmented Generation)は、大規模言語モデル(LLM)の生成能力と外部知識ソースからの情報検索を組み合わせたアプローチです。

RAGの基本的な流れ

  1. ユーザーのクエリを受け取る
  2. クエリに関連する情報を外部知識ソースから検索する
  3. 検索された関連情報をLLMのプロンプト/コンテキストに追加する
  4. 拡張されたコンテキストを基にLLMが回答を生成する

この流れの中で、特に重要なのが2番目のステップである「検索」部分です。ここで、FaissやElasticSearchといった検索技術が登場します。

検索技術の種類と特徴

RAGシステムにおける検索は、大きく分けて以下の二つのアプローチがあります:

  1. キーワード検索(Lexical Search): テキストの文字列パターンを基にした検索
  2. セマンティック検索(Semantic Search): テキストの意味や文脈を考慮した検索

それぞれのアプローチに対応する代表的な技術が、ElasticSearchとFaissです。

ElasticSearch:キーワード検索の代表格

ElasticSearchとは

ElasticSearchは、Luceneを基にした全文検索エンジンで、分散システムとして大規模なデータに対する高速な検索を提供します。

ElasticSearchの主な特徴

  • インバーテッドインデックス: 単語から文書へのマッピングを構築し、高速なキーワード検索を実現
  • 多様なクエリタイプ: 完全一致、部分一致、あいまい検索、正規表現など
  • 構造化データの検索: フィールドベースの検索やフィルタリング
  • スコアリング機能: TF-IDFやBM25などのアルゴリズムによる関連性スコア計算

RAGにおけるElasticSearchの役割

ElasticSearchは主に以下のような役割を果たします:

  1. キーワードベースの精確な検索: 特定の用語や表現を含む文書を高精度で検索
  2. 構造化メタデータによるフィルタリング: 日付、著者、カテゴリなどでの絞り込み
  3. テキスト前処理: トークン化、ステミング、シノニム処理など

Faiss:ベクトル検索の強力なツール

Faissとは

FaissはFacebook AI Research(現Meta AI)によって開発された、大規模な高次元ベクトルの類似性検索ライブラリです。

Faissの主な特徴

  • ベクトル類似度検索: コサイン類似度やユークリッド距離に基づく検索
  • 近似最近傍探索(ANN): HNSW、IVF、PQなどの効率的なアルゴリズム
  • 大規模データセットの高速処理: 数十億のベクトルでも効率的に検索可能
  • GPUサポート: 並列処理による高速化

RAGにおけるFaissの役割

Faissは主に以下のような役割を果たします:

  1. セマンティック検索: 意味的に類似したコンテンツの検索
  2. 埋め込みベクトルのインデックス管理: エンベディングモデルで生成したベクトルの効率的な検索
  3. 意味的類似性に基づく関連文書取得: キーワードの一致に頼らない検索

RAGシステムにおけるFaissとElasticSearchの連携

RAGのシステム構成

RAGシステムの検索部分(Retriever)を詳細に見ていくと、以下のような構成要素があります:

[データソース] → [前処理] → [インデックス構築] → [検索] → [LLMへの入力]

この中で、FaissとElasticSearchは主に「インデックス構築」と「検索」の部分で使用されます。

ハイブリッド検索アプローチ

多くの実用的なRAGシステムでは、FaissとElasticSearchを併用した「ハイブリッド検索」が採用されています。

ハイブリッド検索の実装パターン

  1. 並列検索方式

    • FaissとElasticSearchで並行して検索し、結果をマージ
    • 各エンジンの検索結果にスコアリング・重み付けを行い統合
    # 疑似コード
    elastic_results = elastic_search.query(query_text)
    query_vector = embedding_model.encode(query_text)
    faiss_results = faiss_index.search(query_vector, top_k=10)
    
    # 結果のマージと再ランキング
    merged_results = merge_and_rerank(elastic_results, faiss_results)
    
  2. カスケード検索方式

    • まず一方のエンジンで検索し、その結果をもう一方で絞り込み
    • 例:ElasticSearchで大まかにフィルタリングし、Faissで意味的に最も関連の高いものを特定
    # 疑似コード
    # まずElasticSearchで候補を絞る
    candidates = elastic_search.query(query_text, size=100)
    
    # 候補のIDリストを取得
    candidate_ids = [doc['_id'] for doc in candidates]
    
    # 候補のベクトルをFaissで検索
    query_vector = embedding_model.encode(query_text)
    final_results = faiss_index.search(query_vector, ids=candidate_ids, top_k=10)
    
  3. クエリタイプ別の振り分け

    • クエリの特性に基づいて適切な検索エンジンを選択
    • 例:事実確認系クエリはElasticSearch、概念探索系クエリはFaiss
    # 疑似コード
    query_type = classify_query(query_text)
    
    if query_type == "factual":
        results = elastic_search.query(query_text)
    elif query_type == "conceptual":
        query_vector = embedding_model.encode(query_text)
        results = faiss_index.search(query_vector, top_k=10)
    else:
        # ハイブリッド検索
        results = hybrid_search(query_text)
    

具体的な実装例:医療文書検索RAG

医療分野でのRAGシステムを例に、FaissとElasticSearchの連携を見てみましょう。

1. データ前処理とインデックス構築

# 医療文書のチャンキングと埋め込み生成
chunks = []
for document in medical_documents:
    # セマンティックチャンキング
    doc_chunks = semantic_chunking(document, chunk_size=500)
    chunks.extend(doc_chunks)

# ElasticSearchインデックス構築
for chunk in chunks:
    elastic_client.index(
        index="medical_documents",
        body={
            "content": chunk.text,
            "document_id": chunk.doc_id,
            "section": chunk.section,
            "metadata": chunk.metadata
        }
    )

# Faissインデックス構築
embeddings = []
for chunk in chunks:
    # ClinicalBERTなどの医療特化モデルでエンベディング生成
    embedding = clinical_bert.encode(chunk.text)
    embeddings.append(embedding)

# HNSWアルゴリズムでFaissインデックス構築
dimension = len(embeddings[0])
faiss_index = faiss.IndexHNSWFlat(dimension, 32)  # M=32
faiss_index.add(np.array(embeddings))

2. ハイブリッド検索の実装

def hybrid_search(query, alpha=0.7):
    """
    ハイブリッド検索の実装
    alpha: Faiss結果の重み (0-1)
    """
    # ElasticSearch検索
    elastic_results = elastic_client.search(
        index="medical_documents",
        body={
            "query": {
                "multi_match": {
                    "query": query,
                    "fields": ["content^3", "section"],
                    "fuzziness": "AUTO"
                }
            },
            "size": 20
        }
    )
    
    # Faiss検索
    query_vector = clinical_bert.encode(query)
    faiss_distances, faiss_indices = faiss_index.search(
        np.array([query_vector]), k=20
    )
    
    # 結果のマージと再スコアリング
    merged_results = []
    seen_ids = set()
    
    # Elastic結果の処理
    for hit in elastic_results["hits"]["hits"]:
        doc_id = hit["_source"]["document_id"]
        if doc_id in seen_ids:
            continue
        
        seen_ids.add(doc_id)
        elastic_score = hit["_score"] / 100  # スコア正規化
        merged_results.append({
            "id": doc_id,
            "content": hit["_source"]["content"],
            "elastic_score": elastic_score,
            "faiss_score": 0,
            "final_score": (1 - alpha) * elastic_score
        })
    
    # Faiss結果の処理
    for idx, (distance, vector_idx) in enumerate(zip(faiss_distances[0], faiss_indices[0])):
        chunk = chunks[vector_idx]
        doc_id = chunk.doc_id
        
        # 類似度スコアに変換 (距離→類似度)
        similarity = 1 - min(distance, 1.0)
        
        # 既存結果の更新または新規追加
        found = False
        for result in merged_results:
            if result["id"] == doc_id:
                result["faiss_score"] = similarity
                result["final_score"] += alpha * similarity
                found = True
                break
        
        if not found and len(merged_results) < 20:
            merged_results.append({
                "id": doc_id,
                "content": chunk.text,
                "elastic_score": 0,
                "faiss_score": similarity,
                "final_score": alpha * similarity
            })
    
    # 最終スコアでソート
    merged_results.sort(key=lambda x: x["final_score"], reverse=True)
    return merged_results[:10]  # 上位10件を返す

3. RAGへの統合

def generate_response(query):
    """
    RAGシステムの実装
    """
    # 検索
    search_results = hybrid_search(query)
    
    # コンテキスト構築
    context = ""
    for idx, result in enumerate(search_results):
        context += f"[{idx+1}] {result['content']}\n\n"
    
    # プロンプト構築
    prompt = f"""あなたは医療AIアシスタントです。
以下の情報を参考に、ユーザーの質問に回答してください。
情報がない場合は「情報がありません」と回答し、推測しないでください。
各情報源を[1]、[2]のように明示してください。

検索結果:
{context}

質問: {query}

回答:"""
    
    # LLMによる生成
    response = llm_model.generate(prompt)
    
    return response

Faiss vs ElasticSearch:使い分けのガイドライン

RAGシステムにおいて、FaissとElasticSearchをどのように使い分けるべきでしょうか?以下にガイドラインをまとめます。

ElasticSearchが適している場合

  • 精確なキーワードマッチングが必要:特定の専門用語や正確な表現を含む検索
  • 構造化検索が重要:フィールドベースの検索やフィルタリングが必要
  • 複雑なクエリロジック:ブール演算子や正規表現を用いた高度な検索条件
  • フィルタリングが主目的:日付範囲、カテゴリなどによる絞り込み

Faissが適している場合

  • 意味的類似性が重要:言い換えや概念的な近さを考慮した検索
  • 大規模なベクトルデータセット:数百万〜数十億のエンベディングを扱う場合
  • 類似文書検索:「これに似た文書を探す」タイプの検索
  • 多言語検索:同じ概念を異なる言語で表現した文書の検索

ハイブリッドアプローチが効果的な場合

  • 専門分野の知識ベース:医療、法律、技術文書など専門用語と概念理解の両方が重要
  • 多様なクエリパターン:事実質問と概念質問が混在するシステム
  • 高精度が求められるミッションクリティカルなアプリケーション
  • 大規模で多様なコンテンツを持つナレッジベース

パフォーマンス最適化のポイント

RAGシステムでFaissとElasticSearchを効率的に活用するためのパフォーマンス最適化ポイントをいくつか紹介します。

Faissの最適化

  1. 適切なインデックスタイプの選択

    • 小〜中規模データセット(〜100万ベクトル): IndexFlatL2, IndexHNSWFlat
    • 大規模データセット(100万〜): IndexIVFFlat, IndexIVFPQ
    • 超大規模データセット(10億〜): IndexHNSW + PQ量子化
  2. HNSWパラメータのチューニング

    • M(接続数): 通常16〜64(大きいほど精度向上だがメモリ消費増加)
    • efConstruction(構築時探索幅): 通常40〜400
    • efSearch(検索時探索幅): クエリ重要度に応じて動的調整(40〜400)
  3. ベクトル量子化による効率化

    • 8ビット量子化でメモリ使用量を75%削減可能
    • ScalarQuantizerやProductQuantizerの活用

ElasticSearchの最適化

  1. 適切なインデックス設定

    • シャードとレプリカの最適な数の設定
    • refresh_intervalの調整によるインデックス更新頻度の最適化
  2. マッピング最適化

    • フィールドタイプの適切な設定(text, keyword, date等)
    • アナライザとトークナイザの最適化
  3. クエリパフォーマンス改善

    • フィルタリングの活用(query vs filter)
    • キャッシュ戦略の最適化

ハイブリッド検索の最適化

  1. 動的な重み付け

    • クエリタイプに応じたFaissとElasticSearchの結果の重み付け調整
    • ユーザーフィードバックに基づく適応的調整
  2. 段階的検索戦略

    • 粗い検索→精密検索の2段階アプローチ
    • バッチ処理とキャッシング

RAGの進化:マルチモーダルとエージェント統合

最後に、FaissとElasticSearchを活用したRAGの将来展望について触れておきましょう。

マルチモーダルRAG

テキストだけでなく、画像や音声など複数のモダリティを統合したRAGシステムが登場しています。

# マルチモーダルRAGの例(画像+テキスト)
def multimodal_search(text_query, image=None):
    # テキストエンベディング生成
    text_embedding = text_encoder.encode(text_query)
    
    # 画像がある場合は画像エンベディング生成
    if image is not None:
        image_embedding = image_encoder.encode(image)
        # マルチモーダル融合(シンプルな例:平均)
        query_embedding = (text_embedding + image_embedding) / 2
    else:
        query_embedding = text_embedding
    
    # Faissでの検索
    results = faiss_index.search(query_embedding, k=10)
    
    return results

エージェント型RAG

複数の検索戦略を動的に選択・組み合わせるエージェント型RAGも研究が進んでいます。

# エージェント型RAGの疑似コード
def agent_rag(query):
    # クエリ分析
    query_properties = analyze_query(query)
    
    # 検索戦略の選択
    if query_properties["type"] == "factual":
        # 事実確認にはElasticSearchが適している
        results = elastic_search.query(query)
    elif query_properties["type"] == "conceptual":
        # 概念検索にはFaissが適している
        query_vector = embedding_model.encode(query)
        results = faiss_index.search(query_vector)
    elif query_properties["type"] == "complex":
        # 複雑なクエリにはハイブリッド検索
        results_1 = elastic_search.query(query)
        # 検索結果に基づいてクエリを再構築
        refined_query = refine_query(query, results_1)
        # 再構築クエリでFaiss検索
        query_vector = embedding_model.encode(refined_query)
        results_2 = faiss_index.search(query_vector)
        # 結果を統合
        results = merge_results(results_1, results_2)
    
    # LLMへの入力生成
    context = format_for_llm(results)
    response = llm_model.generate(query, context)
    
    return response

まとめ

この記事では、Faiss、ElasticSearch、RAGの関係性について詳しく解説しました。

  • RAGはLLMの生成能力と外部知識の検索を組み合わせたアプローチ
  • ElasticSearchはキーワードベースの検索に強み
  • Faissはベクトル類似度に基づくセマンティック検索に特化
  • ハイブリッド検索により、両者の強みを組み合わせることが可能

RAGシステムの設計において、検索コンポーネントの選択と最適化は極めて重要です。ユースケースと要件に応じて、FaissとElasticSearchを適切に組み合わせることで、より高精度で柔軟な知識アクセスを実現できます。

今後も、マルチモーダル対応やエージェント統合など、RAG技術はさらに発展していくことでしょう。検索技術と生成AIの融合がもたらす可能性は、まだ始まったばかりです。

Discussion