以下の内容はhttps://acro-engineer.hatenablog.com/entry/2024/09/18/120000より取得しました。


RAGの処理で、リランクとベクトル検索でできることの違いを検証/解説してみる

こんにちは。テニスしすぎて日焼けがすごいSsk1029Takashiです。

私は普段、生成AIを活用したRAGソリューションの開発をしているのですが、RAGでは特に検索部分の調整が重要になります。
今回はその検索の中で出てくるリランクに焦点を当てて、ベクトル検索と比較してどのような特徴があるのかというところを、検証を交えて解説していきます。

概要

RAGの検索部分では、よくベクトル検索が利用されます。
理由としては、入力が基本的に質問形式になりキーワードで入力されることが多い通常の検索よりも適している、などいくつか考えられます。

ただし、実際にRAGを試してみるとわかりますが、RAGシステムではベクトル検索だけでは検索精度の面で苦労することも多いです。
そこで解決方法の一つとして考えられているのが、ベクトル検索とリランクとの併用になります。
今回は、なぜRAGはベクトル検索だけだと苦労が多いのか、その苦労をリランクが解決できるのかを、実装・検証を交えてみていきます。

リランクとは?

まず、検索におけるリランクについて簡単に説明しておきます。
リランクとは一度検索した結果を再度順位付けすることを指します。

どのような効果があるかというと、より詳細にほしい情報を結果の上位に持ってくることが可能になります。
手法自体は様々なものがありますが、ここではリランカ―モデルを使用したものを例にします。
リランクの特徴として、より詳細に入力クエリの意図に近いものを上位に持ってくることができるというものがあります。

ただし、リランクは質問とドキュメントごとにスコアを計算する都合上、処理速度が遅く検索対象全体に対して実行することは推奨されていません。
反面、ベクトル検索は精度に関してはリランクより劣りますが、処理速度が速いため、検索対象全体に対しても問題なく検索できます。

そのため、今現在の主流は以下のように組み合わせて利用する手法になります。
①ベクトル検索でおおまかに質問に近いトピックの文章を広く検索して上位100件など多めに結果を取得する
②ベクトル検索の取得結果を、リランクを使って並べ替える。

ここで疑問になるのは、リランクがベクトル検索よりも精度が良いというのは、具体的にどのような違いがあるかということです。
なので、次の章ではリランクはベクトル検索と比較してどのように精度が良いのかというところを検証してみましょう。

実験

環境・使用するモデル

今回の実験はcollaboratory上で実施します。
使用するモデルはベクトルにはmultilingual-e5-baseを使用します。
日本語に対応しており、精度も日本語では他モデルに対して比較的良いとされています。
huggingface.co
リランカーモデルではjapanese-reranker-cross-encoder-large-v1を使用します。
huggingface.co
このモデルは日本語に特化した数少ないリランク専用のモデルになります。

実験内容

今回は宿のレビュー検索を想定した簡単な実験を行います。
まず、以下のレビュー文章を用意します。

No 文章
1 この宿の温泉は、まさに極上の癒しでした。源泉かけ流しの湯はとても柔らかく、肌がすべすべになる感じがしました。露天風呂からは美しい山々の景色が広がり、夜には満天の星空を眺めながらゆっくりと浸かることができました。何度でも訪れたくなる温泉です。
2 宿の温泉はとても気持ち良く、リラックスできました。内湯と露天風呂があり、それぞれ異なる趣があります。特に露天風呂から見える庭園の景色が素晴らしく、四季折々の美しさを楽しむことができます。お湯も適温で、長時間浸かっていても疲れませんでした。
3 温泉が自慢の宿とのことで期待していましたが、期待以上でした。天然温泉の源泉をそのまま使用しているので、お湯の質がとても良く、温まった後もポカポカが続きました。貸切風呂も予約して利用しましたが、プライベートな空間で贅沢なひとときを過ごすことができました。
4 温泉は広々としていて、開放感が抜群でした。露天風呂からは海が一望でき、波の音を聞きながらゆっくりと過ごすことができました。お湯も熱すぎず、じっくりと温まることができてとても満足です。また、日帰り利用も可能なので、気軽に訪れることができるのも嬉しいポイントです。
5 温泉は硫黄の香りが心地よく、いかにも温泉に来たという実感がありました。お湯の効能も高く、肌がツルツルになるのを感じました。複数の湯船があり、時間帯によっては貸切状態になることもあり、贅沢な気分を味わえました。また、湯上がりに提供される冷たいドリンクも嬉しいサービスでした。
6 宿の食事はまさに芸術品のようでした。地元の新鮮な食材をふんだんに使った会席料理は、見た目も美しく、一品一品が丁寧に作られているのが伝わってきました。特に旬の魚介を使った刺身は絶品で、これだけでもまた訪れたいと思いました。
7 夕食は地元の名物料理が盛りだくさんで、大満足でした。特に炭火焼きでいただいた和牛ステーキは口の中でとろける美味しさで、何度もおかわりしたくなるほどでした。朝食も種類が豊富で、地元の野菜を使ったサラダや、手作りの豆腐が美味しかったです。
8 夕食はコース料理で、どれも美味しかったのですが、特に印象に残ったのは地元で採れた野菜を使った前菜と、自家製のデザートです。食材の味を活かしたシンプルな調理法で、素材の良さが際立っていました。朝食もバランスが良く、特に焼きたてのパンが絶品でした。
9 宿の食事は期待以上でした。海の近くということもあり、新鮮な魚介類が豊富に使われていて、お刺身や煮魚がとても美味しかったです。夕食は量もたっぷりで、どの料理も心のこもった味付けでした。朝食の和食もとても美味しく、特に温泉卵が絶品でした。
10 夕食は地元の食材をふんだんに使った創作料理で、どの料理も工夫が感じられました。特に地元産の野菜とお肉を使ったグリル料理が絶品で、素材の味がしっかりと引き立っていました。朝食も手作りのジャムや焼き立てのパンなど、こだわりが感じられる内容で、大変満足しました。

上記のレビュー文章は1~5は温泉について言及していて、6~10は食事について言及しているレビューです。
このレビュー記事から「お肉の料理がおいしい宿」というクエリが入力される場合を考えます。
想定としては上記の中だとNo. 7とNo. 10の記事が取得できることを期待したいですが、ベクトル検索とリランクの結果がそれぞれどうなるのかを比較してみます。

ベクトル検索

ベクトル検索ではe5-baseを使用して変換したベクトルを、faissを使ってベクトル検索します。
faissで検索する部分はIndexFlatL2を使用して検索するだけなので、コードは省略します。
IndexFlatL2はL2ノルムを利用した逐次検索のため、シンプルに全件に対してL2距離を算出します。

import torch.nn.functional as F

from torch import Tensor
from transformers import AutoTokenizer, AutoModel

def average_pool(last_hidden_states: Tensor,
                 attention_mask: Tensor) -> Tensor:
    last_hidden = last_hidden_states.masked_fill(~attention_mask[..., None].bool(), 0.0)
    return last_hidden.sum(dim=1) / attention_mask.sum(dim=1)[..., None]

input_texts = [
    <各文章のリスト>
]

# モデルを定義
tokenizer = AutoTokenizer.from_pretrained('intfloat/multilingual-e5-base')
model = AutoModel.from_pretrained('intfloat/multilingual-e5-base')
# ベクトルに変換した結果を取得 
batch_dict = tokenizer(input_texts, max_length=512, padding=True, truncation=True, return_tensors='pt')
outputs = model(**batch_dict)
embeddings = average_pool(outputs.last_hidden_state, batch_dict['attention_mask'])
embeddings = F.normalize(embeddings, p=2, dim=1)

検索の結果としてトップ5件を並べると以下のようになりました。
この場合L2距離がスコアになるため、低いものほど近しいと判定されたドキュメントになります。

レビューNo. スコア
6 0.26516113
9 0.28850213
7 0.28850213
10 0.28850213
2 0.28850213

おおむね食事について言及しているレビューを取得できていますが、本来はトップに来てほしい肉料理について言及しているNo.7は3番目の表示順になっています。
ベクトル検索ではこのようにクエリの意図からは異なる文章がトップになることがよくあります。

リランクの場合

リランクの場合を見てみましょう。
リランクはクエリ+レビュー文章を入力にして、10件分それぞれのスコアを算出し、スコアが高いものほど正しいと判断されたデータになります。

from sentence_transformers import CrossEncoder
import torch

# モデルを定義する
MODEL_NAME = "hotchpotch/japanese-reranker-cross-encoder-large-v1"
device = "cuda" if torch.cuda.is_available() else "cpu"
model = CrossEncoder(MODEL_NAME, max_length=512, device=device)

if device == "cuda":
    model.model.half()

query = "お肉の料理がおいしい宿"
# 各レビュー文章に対してスコアを算出する
scores = model.predict([(query, passage) for passage in passages])

そうすると上位5件の結果は以下のようになります。
リランクの場合はベクトル検索とは異なり、スコアが高いものが上位のドキュメントを表します。

レビューNo. スコア
7 0.5723
10 0.5264
6 0.2274
9 0.08154
8 0.01009

肉料理について言及しているNo. 7とNo. 10が上位に来ており、さらにスコアでもかなり他と差がついていることがわかります。

結果

結果としてはベクトル検索を使用した場合はざっくり同じトピックの文章は上位に持って来れていますが、ほしい文章ではないものがトップに来ていたのに対して、リランクではほしい文章をトップに持ってくることができました。
ただし、リランクは前述した通り、対象のドキュメントごとにモデルを使ってスコアを計算するため、メモリなどのリソースが必要でなおかつ時間がかかります。
そのため、大量のドキュメントからおおむね同じトピックについて言及しているドキュメントをベクトル検索で広く取って、取得した結果をリランクするという手法がRAGにおける検索の主流になっています。

まとめ

この記事では、リランクに焦点を当ててベクトル検索とどのように異なるのかを検証してみました。
ベクトル検索とリランクは明確にできることが異なるため、うまく組み合わせて使うことが必要になります。
RAGなどが最近よく使われるようになった影響でさらに研究が進んでいる技術なので今後にも注目ですね。

それではまた。

Acroquest Technologyでは、キャリア採用を行っています。

  • Azure OpenAI/Amazon Bedrock等を使った生成AIソリューションの開発
  • ディープラーニング等を使った自然言語/画像/音声/動画解析の研究開発
  • マイクロサービス、DevOps、最新のOSSクラウドサービスを利用する開発プロジェクト
  • 書籍・雑誌等の執筆や、社内外での技術の発信・共有によるエンジニアとしての成長

少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。

www.wantedly.com




以上の内容はhttps://acro-engineer.hatenablog.com/entry/2024/09/18/120000より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14