ハイブリッド検索とランクフュージョン(セマンティック+キーワード)

概要

Fess の ハイブリッド検索 は、従来のキーワード検索(BM25)と セマンティック(ベクトル)検索 を組み合わせ、両方の検索結果を Rank Fusion で統合することで、より精度が高く関連性の高いランキングを実現します。Rank Fusionは、複数のサーチャーの検索結果を単一の最適化されたランキングに統合します。

Fess 15.8 では、セマンティック検索(コンテンツチャンク+ベクトル検索)はコア機能として提供されます。 有効化すると、セマンティックサーチャーは自動的にRank Fusionに登録されます。 設定方法は セマンティック検索(コンテンツチャンク+ベクトル検索) を参照してください。

Fess のRank Fusion機能は、複数の検索結果を統合して より精度の高い検索結果を提供します。

Rank Fusionとは

Rank Fusionは、複数の検索アルゴリズムやスコアリング手法(例:キーワード/BM25検索とセマンティック/ベクトル検索)の結果を 組み合わせて、単一の最適化されたランキングを生成する技術です。

主な利点:

  • 異なるアルゴリズムの長所を組み合わせる

  • 検索精度の向上

  • 多様な検索結果の提供

対応アルゴリズム

Fess ではRRF(Reciprocal Rank Fusion)アルゴリズムをサポートしています。

RRF (Reciprocal Rank Fusion)

RRFは、各検索結果におけるドキュメントの順位の逆数を合計してスコアを計算します。 複数のサーチャーで取得されたドキュメントは、それぞれのスコアが加算されます。

計算式:

score(d) = Σ 1 / (k + rank(d))
  • k: 順位の影響度を調整する定数パラメーター(デフォルト: 20)

  • rank(d): 各検索結果におけるドキュメントdの順位(0始まり)

  • Σ: ドキュメントdが出現したすべてのサーチャーにわたる合計

注釈

融合アルゴリズムはRRF固定で、他のアルゴリズムに切り替える設定はありません。 また、サーチャーごとの重み付けもサポートしていません。各サーチャーの寄与は同じ重みで 合計されます。ランキングの傾向を調整できるのは rank.fusion.rank_constant のみです。

設定

fess_config.properties

基本設定:

# ウィンドウサイズ(融合対象の結果数)
# 注: paging.search.page.max.size × 2 以上である必要があります。
# 設定値がこの最小値を下回る場合、最小値が自動的に使用されます。
rank.fusion.window_size=200

# RRFのrank_constant(kパラメーター)
rank.fusion.rank_constant=20

# 並列処理のスレッド数(0以下の場合、利用可能なCPUコア数 × 3 ÷ 2 + 1 が使用されます)
rank.fusion.threads=-1

# スコアフィールド名(融合後のスコアを格納するフィールド)
rank.fusion.score_field=rf_score
プロパティ デフォルト 説明
rank.fusion.window_size 200 融合対象として各サーチャーから取得する結果の最大件数。paging.search.page.max.size × 2(デフォルトでは 200)以上である必要があり、下回る場合はこの最小値に自動的に引き上げられます(起動時にWARNログを出力)。
rank.fusion.rank_constant 20 RRF計算式における定数 k。値を大きくすると、上位と下位の順位の差によるスコア差が小さくなります。
rank.fusion.threads -1 複数サーチャーを並列実行する固定スレッドプールのスレッド数。0 以下を指定すると 利用可能なCPUコア数 × 3 ÷ 2 + 1 が自動的に使用されます(整数演算のため小数点以下は切り捨て。例: 4コア→7、5コア→8)。
rank.fusion.score_field rf_score 融合後のスコアを格納する結果ドキュメントのフィールド名。

注釈

設定の反映タイミング

上記4つの設定は、いずれも変更の反映に Fess の再起動が必要です。fess_config.properties から読み込まれた値はJVM内にキャッシュされるため、稼働中にファイルを書き換えても反映されません。

なお rank.fusion.window_size は起動時に一度だけ、rank.fusion.threads は スレッドプールを作成する時点で読み込まれます。スレッドプールは default 以外のサーチャー (セマンティックサーチャーなど)が登録されたときに作成されるため、セマンティック検索が 無効な場合はスレッドプール自体が作成されません。

JVMシステムプロパティ

使用するサーチャーは、JVMシステムプロパティで指定します。 fess.in.sh に以下のように記述します:

FESS_JAVA_OPTS="$FESS_JAVA_OPTS -Drank.fusion.searchers=default,semantic_chunk"

fess.in.bat の場合は次のように記述します:

set FESS_JAVA_OPTS=%FESS_JAVA_OPTS% -Drank.fusion.searchers=default,semantic_chunk

このプロパティの動作は以下のとおりです:

  • fess_config.properties ではなく、JVMオプションとして設定します。キー名は rank.fusion.searchers そのものを指定してください。他の設定でよく使われる -Dfess.config.-Dfess.system. を付けた形式(-Dfess.config.rank.fusion.searchers など)は認識されません。

  • JVMオプションの代わりに、管理画面の「システム > 全般」にある「システムプロパティ」欄へ rank.fusion.searchers=default,semantic_chunk のように1行で記述することもできます。 ただしこの欄の値は、同名のシステムプロパティがまだ設定されていない場合にのみ適用されます。 そのため -D で指定済みの場合はJVMオプションが優先され、いったん適用された値を変更する 場合は Fess の再起動が必要です。

  • default は標準のキーワード検索を行うサーチャーで、常に利用できます。

  • サーチャー名は、実装クラス名から Searcher を取り除き、スネークケースの小文字に変換した ものです(SemanticChunkSearchersemantic_chunk)。コア内蔵のセマンティック サーチャー(セマンティック検索(コンテンツチャンク+ベクトル検索))は semantic_chunk という名前で登録されます。

  • このプロパティを指定しない場合は、登録されているすべてのサーチャーが使用されます。指定した名前がいずれのサーチャーにも一致しない場合は、default サーチャーのみが使用されます。コア内蔵のセマンティックサーチャー(セマンティック検索(コンテンツチャンク+ベクトル検索))を利用する場合は、通常このプロパティの指定自体が不要です。

  • Rank Fusionによる結果の融合は、利用可能なサーチャーが2つ以上ある場合に実行されます。サーチャーが1つだけの場合は、融合は行われず通常の検索結果が返されます。

警告

Fess 15.7 以前の fess-webapp-semantic-search プラグインを使っていた場合、このプロパティに -Drank.fusion.searchers=default,semantic を指定するよう案内されていました。プラグインが 登録していたサーチャー名は semantic で、15.8 でコアに統合されたセマンティックサーチャーの 名前 semantic_chunk とは 別物 です。この設定を 15.8 でも引き継いだままにすると、 allowlist に semantic_chunk が含まれないため、コア内蔵のセマンティック検索(コンテンツ チャンク+ベクトル検索)が 一切動作しないまま 通常のキーワード検索結果だけが返り続けます (起動時に警告ログは出力されますが、個々の検索リクエストでの除外自体は DEBUG ログにしか 現れません)。default,semantic を指定している場合は、この設定を削除するか semantic_chunk を追加してください。詳細は セマンティック検索(コンテンツチャンク+ベクトル検索) の 「15.7 以前からアップグレードする場合の移行」を参照してください。

ハイブリッド検索との連携

Rank Fusionは、キーワード検索とセマンティック検索を組み合わせた ハイブリッド検索で特に効果を発揮します。 セマンティック検索を利用するには、コンテンツチャンク機能を設定したうえで content_chunker.search.enabled=true を設定します。

警告

content_chunker.enabledcontent_chunker.search.enabled などの content_chunker.* の設定は、fess_config.properties ではなく システムプロパティ です。conf/system.properties に記述するか、 -Dfess.system.content_chunker.search.enabled=true のようにJVMオプションとして 指定してください。fess_config.properties に記述しても反映されません。 また content_chunker.search.enabled は起動時にのみ評価されるため、 有効化後は Fess の再起動が必要です。

詳細は セマンティック検索(コンテンツチャンク+ベクトル検索) を参照してください。

融合結果の確認

Rank Fusionが実際に動作しているかは、検索結果に付与される以下の2つのフィールドで確認できます。

フィールド 内容
searcher そのドキュメントを取得したサーチャー名の配列(例: ["default", "semantic_chunk"])。両方が含まれていれば、キーワード検索とセマンティック検索の双方でヒットしたことを意味します。
rf_score RRFで算出した融合後のスコア。フィールド名は rank.fusion.score_field で変更できます。

どちらも検索時に動的に付与される値で、インデックスには保存されません。 また、既定では /api/v2/search のレスポンスに含まれないため、確認するには fess_config.properties に以下を設定して Fess を再起動してください:

query.additional.api.response.fields=rf_score,searcher

注釈

query.additional.api.response.fields は、v2検索APIのレスポンスに含めてよい フィールドの許可リストに項目を追加する設定です。rolevirtual_host などの アクセス制御用フィールドを追加すると、アクセス制御情報が検索APIの応答に露出するため、 追加しないでください。

ヒット件数への影響

Rank Fusionが実行されると、返される総ヒット件数はメインサーチャー(先頭に登録された default サーチャー)の件数そのままではなく、次のように補正されます:

総ヒット件数 = メインサーチャーの総ヒット件数 + 補正値

補正値は、融合後の上位 window_size ÷ 2 件のうち、メインサーチャーの上位 window_size ÷ 2 件に含まれていなかったドキュメントの件数です。つまり、セマンティック検索 だけが見つけたドキュメントの分だけ件数が増えます。 そのため、同じクエリでもハイブリッド検索の有効・無効でヒット件数が変わることがあります。

なお、メインサーチャーの総ヒット件数が概算値(下限値)として返される場合、この補正は行われません。

使用例

基本的なハイブリッド検索

  1. キーワード検索でBM25スコアを計算

  2. セマンティック検索でベクトル類似度を計算

  3. RRFで両方の結果を融合

  4. 最終的なランキングを生成

検索フロー:

User Query
    ↓
┌──────────────────┬──────────────────┐
│  Keyword Search  │ Semantic Search  │
│    (BM25)        │  (Vector)        │
└────────┬─────────┴────────┬─────────┘
         ↓                  ↓
     Rank List 1        Rank List 2
         └────────┬─────────┘
                  ↓
          Rank Fusion (RRF)
                  ↓
          Final Ranking

パフォーマンス考慮事項

メモリ使用量

  • 複数の検索結果を保持するため、メモリ使用量が増加します。

  • rank.fusion.window_size で融合対象の最大件数を制限できます。メインサーチャー(先頭の default サーチャー)は最大で window_size 件、その他のサーチャーはそれぞれ window_size ÷ サーチャー数 件を取得します(サーチャー数 はメインサーチャーを含む総数で、除算は切り捨てです)。

  • 例えばサーチャーが2つ(defaultsemantic_chunk)で window_size=200 の場合、メインサーチャーが200件、セマンティックサーチャーが100件を取得するため、保持されるドキュメントは最大300件になります。

# 融合対象のウィンドウサイズ
rank.fusion.window_size=200

警告

rank.fusion.window_sizepaging.search.page.max.size × 2 を下回れません。 paging.search.page.max.size が既定の 100 の場合、下限は 200 となり、これは rank.fusion.window_size の既定値と同じです。つまり 既定の構成では window_size を 既定値より小さくできません。小さい値を設定しても起動時にWARNログが出力され、200 に引き上げられます。実際に小さくするには paging.search.page.max.size を先に下げる 必要がありますが、これは検索画面やAPIで1ページに要求できる最大件数も同時に下がります。

処理時間

  • 複数の検索を実行するため、レスポンス時間が増加します。

  • rank.fusion.threads で並列実行のスレッド数を設定します。

# 並列実行のスレッド数(0以下の場合、利用可能なCPUコア数 × 3 ÷ 2 + 1)
rank.fusion.threads=-1

注釈

サーチャーの実行にタイムアウトは設定されていません。応答が返らないサーチャーがあると、 検索リクエストはその完了まで待機します。

サーチャー障害時の動作

いずれかのサーチャーが例外で失敗した場合、そのサーチャーの結果は空として扱われ、 WARNログを出力したうえで、残りのサーチャーの結果だけで融合が続行されます。 検索リクエスト自体はエラーになりません。

ただし、クエリ構文エラー(InvalidQueryException)とページング上限超過 (ResultOffsetExceededException)は例外で、これらはそのままエラーとして返されます。 また、融合が行われない深いページ(開始位置 × 2rank.fusion.window_size 以上に なる位置)では、メインサーチャーで発生した例外はそのまま検索リクエストのエラーになります。

セマンティックサーチャーは、埋め込みプロバイダに接続できない場合や埋め込み処理に失敗した場合、 結果を空として返します。この場合もエラーにはならず、キーワード検索のみの結果になります。

トラブルシューティング

検索結果が期待と異なる

症状: Rank Fusion後の結果が期待と異なる

確認事項:

  1. searcher フィールドを確認する(「融合結果の確認」を参照)。すべてのドキュメントが ["default"] のみの場合、セマンティックサーチャーが結果を返していません。

  2. セマンティック検索がスキップされていないか確認する。検索構文(" : AND など)を 含むクエリのほか、ラベル・ソート・ファセットによる絞り込み、位置情報検索、類似ドキュメント検索 では、セマンティックサーチャーは結果を返さずキーワード検索のみの結果になります。 スキップ条件の詳細は セマンティック検索(コンテンツチャンク+ベクトル検索) を参照してください。

  3. 各検索タイプの結果を個別に確認

  4. rank.fusion.rank_constant の値を調整

  5. 深いページ(開始位置 × 2rank.fusion.window_size 以上になる位置。既定では 101件目以降)では融合が行われず、メインサーチャーのみで検索されます。より多くのページで 融合結果を利用したい場合は rank.fusion.window_size を大きくしてください。

検索が遅い

症状: Rank Fusion有効時に検索が遅くなる

解決方法:

  1. rank.fusion.threads を調整する:

    rank.fusion.threads=4
    
  2. rank.fusion.window_size を減らす。ただし下限(paging.search.page.max.size × 2) を下回れないため、既定の構成では次の2つをセットで設定します:

    paging.search.page.max.size=50
    rank.fusion.window_size=100
    

    1ページに要求できる最大件数も下がる点に注意してください。設定後は再起動が必要です。

メモリ不足

症状: OutOfMemoryError が発生する

解決方法:

  1. 「検索が遅い」と同じ手順で rank.fusion.window_size を減らす

  2. JVMヒープサイズを増やす

参考情報