3
2

前回(https://qiita.com/mebi/items/1f29335a7afb9e92c440 )、生成AIを使った検索でRAGの実装しましてみました。

今回は、RAGを改良したHyDEという手法をためして見たいと思います。


HyDEの仕組み

RAGは、
予め検索対象ドキュメントを生成AIを通しておき、Embeddingというベクトル情報を作成しておきます。
そして、ユーザー入力に対して、Embeddingを作成します。
2つのEmbeddingが近しい(差が0に近い)ドキュメントを検索結果として、そのドキュメントをもとにユーザー質問へ答える・・・というものでした。

ここで、ユーザーは「質問」しているのにRAG側では「回答」を探しており、
そもそも「質問」と「回答」は意味的に近しいのか?という疑問が生まれます。

そしてそのあたりは、生成AIの元データだったり学習のさせ方に依存んしていたり、実質ブラックボックスの状態なので知るすべがありません。

HyDEではこの疑問に答えます。
HyDEは、ユーザーからの「質問」に対して、一度「架空の、質問の答えとなるテキスト、架空の回答」を生成します。
そして、その架空の回答に対してEmbeddingを算出するのです。
以降はRAGの流れとなります。

「質問」と「質問に対する回答」よりも、
「質問に対する架空の回答」と「質問に対する(真の)回答」のほうがマッチしやすそうなのが体感でわかります。
(テキストの文章構造などを想像してみてください)


実装

前回(https://qiita.com/mebi/items/1f29335a7afb9e92c440)
の実装に加えて、HyDEを追加実装します。

まずは、架空のドキュメントを作成する部分です。

# 要件に合わせて具体的に書いたほうが良さそう。
# 例えば、「架空の社内規定を作って」など。
def make_hypothetical_document_prompt(input_text):
    prompt  = "次のユーザー質問への回答や対応が書いてある、架空の文書の内容を出力してください。\n"
    prompt += "ユーザーが、ルールを尋ねているのか、具体的な対応を尋ねているのかに注視して、回答文書の内容を記述してください\n"
    prompt += "・適切に改行を入れてください\n"
    prompt += "# ユーザーの質問 \n\n"
    prompt += input_text + "\n\n"
    prompt += "# あなたの答え"
    return prompt

def make_hypothetical_document(input_text, api_key):
    prompt = make_hypothetical_document_prompt(input_text)
    openai.api_key = api_key
    response = openai.ChatCompletion.create(
        model="gpt-4o",
        messages=[
            {"role": "user",
            "content": prompt},
        ],
    )
    ret_msg = response.choices[0]["message"]["content"].strip()
    return ret_msg

あとはユーザー入力からEmbeddingを作成する処理を
架空回答文書から作成するように差し替えればOK

INPUT_TEXT="ユーザーからの入力を設定します"

# RAG版の実装は以下のコメントアウト。
# input_vector = get_embedding(INPUT_TEXT)
# dists = calc_ebd_dist(df, input_vector)

# HyDE版の実装はこちら。
hy_doc = make_hypothetical_document(INPUT_TEXT, API_KEY)
input_vector = get_embedding(hy_doc)
dists = calc_ebd_dist(df, input_vector)
# 以降は同じ〜。

実装を終えて。

仕組みが簡単で、非常に明快な改良法なので良かったです。

デメリットはAPI呼び出し回数が2倍になることです。
各種要件と相談ですね(gpt-4oがでて、コスト面で制約がゆるくなったので やりやすくはなっています)


Embeddingの出力を見る。

あと、最後にRAG版とHyDE版で、
入力Embedding と 検索ドキュメントのEmbeddingの差を出力してみました。
差 = 2ベクトル間の距離で 0 〜 1.0 です。

そして、これおは何度か入力質問を変えて、
「RAG版が誤った結果を返す、HyDE版が正しい結果を返す」
結果となったときのEmbeddingの差になります。
(これは、うまく性能差を引き出す質問ができずRAGもHyDEも、期待するドキュメントを参照してばっかりいたからです。結果に違いがある質問をするのに苦労しました・・・)

ファイル名 RAG版Embeddingとの差 HyDE版Embeddingとの差
リファレンス1.txt(HyDE版が参照したドキュメント) 0.594 0.503
リファレンス2.txt 0.601 0.512
リファレンス3.txt 0.632 0.579
... ... ...
リファレンス98.txt 0.619 0.560
リファレンス99.txt(RAG版が参照したドキュメント) 0.571 0.530

0に近いほど、よいリファレンスドキュメント検索ができたと言えます。

今回の質問では、リファレンス1.txtを参照してもらいたかったのですが、
RAG版では他のドキュメントがヒットしていました。

RAGからHyDEにすることで、
全体としてEmbeddingの差が0.04〜0.05ほど改善していますが、
より正解に近いドキュメントでは、0.09〜0.10ほど改善しておりまして、

HyDEにすることで、より正解に近いドキュメントのほうが、Embeddingの差がより小さくなる傾向が確認できました。

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2