HUOXIU

上級RAG 08: セルフRAGによる高品質で追跡可能なRAGシステムの構築

編集者注: RAG技術は、外部の知識源を取得・活用することで、生成されるコンテンツの精度と多様性を効果的に向上させることができます。しかし、従来のRAGプロセスには、不要な検索によって計算リソースが浪費されたり、関連性のないコンテンツや誤った情報が混入して生成されるコンテンツの品質に影響を及ぼす可能性があるなど、いくつかの欠点もあります。

本稿では、Reflection Tokenを組み込むことで、言語モデルが特定のニーズに基づいて外部知識を取得するかどうかを動的に決定し、不要な取得操作を大幅に削減できるSelf-RAG技術を紹介します。同時に、Self-RAGは独自の学習プロセスを通じて、流暢で自然であり、事実に基づく知識と整合しているだけでなく、知識源まで追跡可能なコンテンツを生成します。

もちろん、Self-RAG技術の学習プロセスは比較的複雑であり、生成段階で多くの特殊なメカニズムが組み込まれているため、推論コストはある程度増加します。しかし、本論文の著者らは、Reflection Tokenの設計を簡素化したり、異なるモデルサイズの影響を調査したりするなど、Self-RAGを最適化するための提案もいくつか示しており、技術開発の方向性を示しています。

著者 | フロリアン・ジューン

編纂者:岳陽

この記事は、よくある人生のシナリオ、つまりオープンブック試験の受験から始まります。私たちは通常、以下の2つの解答戦略を採用します。

  • 方法 1:よく知っている質問には、すぐに直接答えます。よく知らない質問には、参考書をざっとめくって関連部分を見つけ、頭の中で整理して要約してから、テスト用紙に答えます。
  • 方法2:各問題は教科書の解答を参照する必要があります。まず、関連するセクションを探し、頭の中で整理・要約し、試験用紙に解答を書きます。

明らかに、方法1は受験者に好まれ、最も好まれる方法です。方法2は時間がかかるだけでなく、無関係な情報や誤った情報も混入する可能性があり、受験者が本来得意とする分野であっても混乱や間違いにつながる可能性があります。

しかし、方法2は古典的なRAG(検索→統合→生成)プロセス[1]であるのに対し、方法1は自己RAGプロセス[2]を表しています。本稿では、この問題をさらに検討します。

01 概要

図1はRAGとSelf-RAG[2]の主なプロセスを比較している。

図1:Self-RAGの概要。Self-RAG(右)の主な機能は、検索、批評、生成であり、生成されたテキストが流暢で一貫性があるだけでなく、事実に基づく知識と整合性があり、元の知識源に遡って追跡可能であることを保証します。出典:https://arxiv.org/pdf/2310.11511.pdf

Self-RAG は主に次の 3 つのステップで構成されます。

  1. 必要に応じた取得:モデルが情報を取得する必要がある場合、例えば「アメリカの州名はどのようにして生まれたのか?」というクエリ(図1、右上)のように、モデルの出力には[Retrieve]トークンが含まれます。これは、クエリに関連するコンテンツを取得する必要があることを示します。一方、「『思い出に残る夏休み』というテーマでエッセイを書いてください」という質問(図1、右下)の場合、モデルは何も取得せずに直接回答を生成することを選択します。
  2. 並列生成:モデルはプロンプトと取得されたコンテンツの両方を同時に使用して出力を生成します。プロセス全体を通して、3つのリフレクショントークン(例:[取得])が取得されたコンテンツの関連性を示します。
  3. コンテンツの評価と選択: 手順 2 で生成されたコンテンツを評価し、最適なドキュメント段落を出力として選択します。

上記のモデルは特別にトレーニングされており、そのトレーニング プロセスについてはこの記事の後半で説明することに注意してください。

02 リフレクショントークンの紹介

図 2 に示すように、自己 RAG フレームワークは、生成プロセス中にリフレクション トークンを使用してより正確な制御を行う点で RAG と異なります。

図2:Self-RAGで使用される4種類の反射トークン。各タイプは複数のトークンを用いて出力値を表す。下3行はクリティカルトークンの3つのカテゴリを示し、太字で示されているのは各カテゴリで最も理想的なクリティカルトークンである。x、y、dはそれぞれ入力、出力、関連箇所を表す。出典:Self-RAG[2]

一般的に、自己 RAG は次の 4 つの異なる判断を行います。

  • [取得] : リソース R から追加情報を取得するかどうかを決定する意思決定プロセス。
  • [IsREL] : 与えられたデータセット d に問題 x を解決するために必要な情報が含まれているかどうかを判断するための関連性チェック。
  • [IsSUP] : 生成された応答 y の内容を裏付ける証拠がデータ d に存在するかどうかを確認する検証プロセス。
  • [IsUSE] : 出力は数値スコア(範囲 1 ~ 5)で、5 は問題の解決において生成された応答の有用性の最高レベルを表します。

RAGシステムでは、検索は状況に関わらず実行しなければならない必須の固定ステップです。これに対し、自己RAGでは反射トークンが導入され、LLMの適応性と知性が向上します。テキスト生成中に、LLMが追加情報を必要とする不確実な領域に遭遇した場合、反射トークンに遭遇した時点でテキスト生成タスクを一時停止します。その後、システムは高速かつ正確な情報検索操作を実行し、最終的にLLMは新たに取得した情報を用いて現在のテキスト生成タスクを続行します。

03 コード解説:コード分析による自己RAGの理解

Self-RAG プロセスを直感的に理解するには、まずコードを見て調べ、次にモデルのトレーニング プロセスについて詳しく説明する必要がありま す。

Self-RAGはオープンソース技術[3]であり、よく知られているオープンソースPythonライブラリであるLangchain[4]やLlamaIndexはSelf-RAG機能を実装しています。本稿では、LlamaIndexライブラリ[5]におけるSelf-RAGの具体的な技術的実装を参考に説明します。

3.1 環境設定

まず、環境を設定します。

 (ベース) Florian@instance-1:~$ conda create -n llamaindex python=3.11 (ベース) Florian@instance-1:~$ conda activate llamaindex
(llamaindex) Florian@instance-1:~$ pip install llama-index
(llamaindex) Florian@instance-1:~$ pip インストール huggingface-hub
(llamaindex) Florian@instance-1:~$ huggingface-cli ログイン

インストール後、LlamaIndexのバージョン情報は以下のようになります。ご確認ください。

ラマインデックス 0.10.20 ラマインデックスコア 0.10.20.post2 

論文で提供されている Llama2-7B モデルをダウンロードしてください。モデルのサイズは約 4.08 GB です。

 (llamaindex) Florian@instance-1:~$ huggingface-cli ダウンロード m4r1/selfrag_llama2_7b-GGUF selfrag_llama2_7b.q4_k_m.gguf --local-dir "YOUR_DOWNLOAD_MODEL_DIR" --local-dir-use-symlinks False
(llamaindex) Florian@instance-1:~$ ls "YOUR_DOWNLOAD_MODEL_DIR"selfrag_llama2_7b.q4_k_m.gguf 

3.2 テストコード

テストコードは以下のとおりです。初回実行時にはSelfRAGPack[5]をダウンロードする必要があります。

インポートOS
os.environ["OPENAI_API_KEY"] = "YOUR_OPENAI_API_KEY" llama_index.coreからDocument、VectorStoreIndexをインポート
llama_index.core.retrievers から VectorIndexRetriever をインポートします
llama_index.core.readers から SimpleDirectoryReader をインポートします
from pathlib import Path# オプション: download SelfRAGPack# 最初の実行ではSelfRAGPackのダウンロードが必要です。# 以降の実行ではこれをコメントアウトできます。from llama_index.core.llama_pack import download_llama_pack
download_llama_pack( "SelfRAGPack", "./self_rag_pack")
from llama_index.packs.self_rag import SelfRAGQueryEngine# Llama2モデルが以前にダウンロードされ保存されたディレクトリ。download_dir = "YOUR_DOWNLOAD_MODEL_DIR"# テストドキュメントを作成するdocuments = [
書類(
text="陸上では「よちよち歩き」として知られるペンギンの群れが、南極の氷の上をよちよち歩き、タキシードのような羽毛が雪の上に映えていました。"
)、
書類(
text="すべてのペンギン種の中で最も背が高い皇帝ペンギンは、他のどの鳥よりも深く潜ることができ、500 メートル以上の深さまで到達します。"
)、
書類(
text="ペンギンの白黒の色合いは、カウンターシェーディングと呼ばれるカモフラージュの一種です。上から見ると、黒い背中が海の深みと溶け合い、下から見ると、白い腹が明るい海面と調和します。"
)、
書類(
text="ペンギンは直立姿勢ですが、飛ぶことができない鳥です。翼がひれに進化しており、泳ぎが得意です。"
)、
書類(
text="最速の種であるジェンツーペンギンは、ひれと流線型の体を使って水を切り裂きながら、時速 36 キロメートルで泳ぐことができます。"
)、
書類(
text="ペンギンは社会性鳥類です。多くの種は繁殖のために大規模なコロニーを形成し、その数は数万羽に達することもあります。"
)、
書類(
text="興味深いことに、ペンギンは優れた聴覚を持っており、騒がしいコロニーの中で仲間や雛を識別するために独特の鳴き声を頼りにしています。"
)、
書類(
text="ペンギンの中で最も小さい種であるリトルブルーペンギンは、体高が約 40 cm で、オーストラリア南部とニュージーランドの海岸沿いに生息しています。"
)、
書類(
text="繁殖期には、オスのコウテイペンギンは何か月にもわたって厳しい南極の冬を耐え、絶食して卵を温め、その間メスは海で狩りをします。"
)、
書類(
text="ペンギンはさまざまな魚介類を食べます。彼らの食事は主に、潜水遠征で​​捕まえた魚、イカ、オキアミで構成されています。"
)、
]
index = VectorStoreIndex.from_documents(documents)# シンプルなリトリーバーを設定するretriever = VectorIndexRetriever(
インデックス=インデックス、
類似度トップk=10、
)
model_path = Path(download_dir) / "selfrag_llama2_7b.q4_k_m.gguf" query_engine = SelfRAGQueryEngine(str(model_path), retriever, verbose=True)# 検索なし exampleresponse = query_engine.query("高慢と偏見という本はどのジャンルですか?")# 検索 exampleresponse = query_engine.query("最も小さいペンギンの身長はどれくらいですか?") 

テスト コードの実行結果は次のとおりです (llama_cpp のデバッグ情報のほとんどは削除されています)。

 ......モデルメタデータ: {'tokenizer.ggml.add_eos_token': 'false', 'tokenizer.ggml.eos_token_id': '2', 'general.architecture': 'llama', 'llama.rope.freq_base': '10000.000000', 'llama.context_length': '4096', 'general.name': 'LLaMA v2', 'tokenizer.ggml.add_bos_token': 'true', 'llama.embedding_length': '4096', 'llama.feed_forward_length': '11008', 'llama.attention.layer_norm_rms_epsilon': '0.000010', 'llama.rope.dimension_count': '128', 'tokenizer.ggml.bos_token_id': '1', 'llama.attention.head_count': '32', 'llama.block_count': '32', 'llama.attention.head_count_kv': '32', 'general.quantization_version': '2', 'tokenizer.ggml.model': 'llama', 'general.file_type': '15'}フォールバックチャット形式を使用: なしllama_print_timings: ロード時間 = 4887.53 msllama_print_timings: サンプル時間 = 11.29 ms / 22回実行 (トークンあたり0.51 ms、1秒あたり1947.76トークン)llama_print_timings: プロンプト評価時間 = 4887.46 ミリ秒 / 24 トークン ( 203.64 ミリ秒 / トークンあたり 4.91 トークン / 秒)llama_print_timings: 評価時間 = 5883.27 ミリ秒 / 21 回実行 ( 280.16 ミリ秒 / トークンあたり 3.57 トークン / 秒)llama_print_timings: 合計時間 = 10901.84 ミリ秒 / 45 トークン最終回答: 『高慢と偏見』はジェーン・オースティンの恋愛小説です。llama_print_timings: ロード時間 = 4887.53 ミリ秒llama_print_timings: サンプル時間 = 11.74 ミリ秒 / 20 回実行 ( 0.59 ミリ秒 / トークンあたり 1703.29 トークン / 秒)llama_print_timings: プロンプト評価時間 = 7473.66 ミリ秒 / 37トークン (トークンあたり 201.99 ミリ秒、1 秒あたり 4.95 トークン)llama_print_timings: 評価時間 = 5414.34 ミリ秒 / 19 実行 (トークンあたり 284.96 ミリ秒、1 秒あたり 3.51 トークン)llama_print_timings: 合計時間 = 13076.88 ミリ秒 / 56 トークン入力: ### 命令: 最も小さいペンギンの身長はどれくらいですか? ### 応答:[取得]<段落>ペンギンはさまざまな魚介類を食べます。彼らの食事は主に、ダイビング遠征で捕まえる魚、イカ、オキアミで構成されています。</段落>予測: [関連]最小のペンギンの種の身長は、種によって異なります。[サポートなし / 矛盾][有用性:5]スコア: 1.421359834297436710/10段落完了評価を終了ベストアンサーを選択しました: [関連]最小のペンギンの種は、リトルブルーペンギン (フェアリーペンギンとも呼ばれます) で、身長は約 40 センチメートル (16 インチ) まで成長します。 

最初のクエリでは取得操作は必要ないのに対し、2 番目のクエリではすでに取得操作が実行され、出力コンテンツが評価されていることがわかります。

テストコードを理解する鍵は、SelfRAGQueryEngine[6]クラスの実装を理解することです。それでは、このクラスを詳しく見ていきましょう。

3.3 SelfRAGQueryEngineクラス

まず、llama_cppを使ってLlama2-7Bモデルをロードするために主に使用されるコンストラクタ[7]を見てみましょう。

クラスSelfRAGQueryEngine(CustomQueryEngine):
「シンプルな短縮形式の自己 RAG クエリ エンジンです。」
llm: 任意 = フィールド(デフォルト = なし、説明 = "llm")
リトリーバー: BaseRetriever = フィールド(デフォルト=なし、説明="リトリーバー")
generate_kwargs: Dict = Field(default=None, description="llm 生成引数")
詳細: bool = Field(default=True, description="Verbose.") def __init__(
自己、
model_path: 文字列,
        レトリーバー: BaseRetriever、
        詳細: bool = False、
model_kwargs: 辞書 = なし、
generate_kwargs: 辞書 = なし、
**kwargs: 任意、
) -> なし:
「「「初期化パラメータ」」」
super().__init__(verbose=verbose, **kwargs)
model_kwargs = model_kwargs または _MODEL_KWARGS
self.generate_kwargs = generate_kwargs または _GENERATE_KWARGS を試してください: from llama_cpp import Llama except ImportError: raise ImportError(_IMPORT_ERROR_MSG)
self.llm = Llama(model_path=モデルパス、verbose=詳細、**model_kwargs)
self.retriever = レトリーバー

次に、クエリを処理するための関連関数を紹介します[8]。主な処理は図3に示すとおりです。

図3: クエリ処理の主なフロー。画像は著者提供。

読者の理解を深めるために、重要なセクションに注釈が付けられています。

 def custom_query(self, query_str: str) -> レスポンス:
「セルフRAGを実行します。」
# Llama2 モデルを使用して応答を取得します。
レスポンス = self.llm(プロンプト=_format_prompt(query_str), **_GENERATE_KWARGS)
回答 = 応答["選択肢"][0]["テキスト"]
source_nodes = [] # 取得が必要かどうかを判断します。
回答に「[取得]」が含まれている場合: self.verbose が含まれている場合:
print_text("取得が必要です\n", color="blue") # 図 1 のステップ 1、必要に応じて取得します。
documents = self.retriever.retrieve(query_str) の場合、self.verbose:
print_text(f"受信済み: {len(documents)} 文書\n", color="blue")
段落 = [
ドキュメント内のドキュメントの_format_prompt(query_str, document.node.text)
self.verbose の場合:
print_text("評価開始\n", color="blue") # 図1のステップ2と3は並列に生成して評価する
# (コードは並列処理を実装していません)
critic_output = self._run_critic(段落)
paragraphs_final_score = critic_output.paragraphs_final_score
llm_response_per_paragraph = critic_output.llm_response_per_paragraph
source_nodes = critic_output.source_nodes の場合、self.verbose:
print_text("評価終了\n", color="blue") # 最も高いスコアを持つ段落を選択して返します。
ベストパラグラフID = 最大(
paragraphs_final_score、キー=paragraphs_final_score.get
)
answer = llm_response_per_paragraph[best_paragraph_id] の場合 self.verbose:
print_text(f"最適な回答を選択しました: {answer}\n", color="blue")
answer = _postprocess_answer(answer) の場合、self.verbose:
print_text(f"最終回答: {answer}\n", color="green") return Response(response=str(answer), source_nodes=source_nodes) 

コードからわかるように、図1の3つのステップすべてが表現されています。ただし、LlamaIndexの自己RAG機能は並列化されていません。興味のある方は、self._run_critic関数を参照してください。この関数は、様々なリフレクショントークンに対応するスコアも処理します。

04 Llama2-7Bモデルのトレーニング方法

これまでにも Llama2-7B モデルを何度も使用してきましたが、この記事ではこのモデルを入手してトレーニングする方法について説明します。

4.1 研修の目的

これにより、言語モデルは反射トークンを含むテキストを生成できるようになります。

4.2 2つのモデル

学習には、クリティカルモデル(C)とジェネレータモデル(M)の2つのモデルが必要です。クリティカルモデルCは主に、教師あり学習タスクにおいてMが必要とするラベル付き教師データを生成します。

ただし、推論プロセス中はモデル M のみを使用する必要があり、モデル C を批判する必要はありません。

4.3 クリティカルモデルC

批判モデルは、反省トークンを生成するように訓練されます。このモデルは、タスク出力にオフラインで反省トークンを挿入し、訓練コーパスを更新するために使用されます。

各テキスト段落にリフレクショントークンを手動でラベル付けするのは非常にコストがかかります。Self-RAGはGPT-4を活用し、各リフレクショントークンの定義、入力、出力に基づいて、それぞれに固有の具体的な指示を割り当てることで、データラベル付けタスクを効率的に完了します。例えば、[retrieval]トークンは、コンテンツ評価を行う際に外部ドキュメントを取得するかどうかをモデルに指示します。

トレーニング データ D_critic を取得したら、以下に示すように、従来の条件付き言語モデルに基づいて機械学習モデルをトレーニングするための目的関数を構築できます。

クリティカルモデルCは、任意の事前学習済み言語モデルを用いて初期化し、さらに微調整することができます。例えば、生成モデルと同じ事前学習済みモデル(Llama 2 7Bなど)を用いて直接初期化することも可能です。

4.4 生成モデルM

図4は、Self-RAGフレームワーク内で、生成モデルを学習するための教師データ(教師あり学習タスク用のラベル付きデータ)がどのように収集されるかを示しています。入力と出力のペア(x​​, y)が与えられると、Self-RAGは検索モデルと批評モデルを用いて元のモデル出力yにラベルを付け、教師データを作成します。yの各セグメントyt(yt∈y)について、以下の処理が行われます。

図4: ジェネレータモデルの学習データが収集される。図中の各条件判断はクリティカルモデルCによって実行される。この画像は著者提供であり、Self-RAG[2]のセクション3.2.2に着想を得たものである。

図4の各条件判断はクリティカルモデルCによって実行されることに注意してください。得られたトレーニングデータは図5に示されています。

図5:Self-RAGの学習例。左の例では外部検索を必要としませんが、右の例では外部文書の検索を必要とするため、関連する文書の段落が挿入されます。出典:Self-RAG[2]。

トレーニング データ D_gen を取得したら、以下に示すように、言語モデルをトレーニングするための標準的な次のトークン予測目的関数を構築できます。

生成モデル M は、出力コンテンツだけでなく、反射トークンも予測する必要があります。

05 自己RAGに関する著者の洞察と考察

全体として、自己RAGはRAG技術の強化と最適化のための新たな視点を提供します。しかしながら、この手法はより複雑な学習プロセスを必要とし、生成フェーズに特別なメカニズムを組み込んでいます。対象の出力テキストだけでなく、様々な種類のフィードバックラベルも生成し、生成中にこれらのラベルに基づいて複数の決定操作を実行します。これにより推論コストが必然的に増加しリアルタイム性能を優先するプロジェクトに深刻な影響を与える可能性があります。

さらに、このフレームワークには大きな改善の余地があります。さらなる議論とイノベーションを促すために、いくつかの提案をさせていただきます。

  • リフレクショントークンをどのように最適化するか? Self-RAGは4種類のリフレクショントークンを設計しました。[Retrieve]トークンを除く他の3つ([IsREL]、[IsSUP]、[IsUSE])にはいくつかの類似点があります。リフレクショントークンの数を減らすか、他のセマンティクスを表現するようにリフレクショントークンを設計することは、検討する価値のある最適化の方向性となる可能性があります。
  • なぜ批判モデルに大規模言語モデル(LLM)を使用するのでしょうか?これは、[IsUSE]のようなトークンが常識的な知識に大きく依存しているからだと思います。回答の有用性を判断することは、より小規模なモデルでも可能なタスクです。しかし、これらの小規模なモデルは通常、特定のトレーニングデータからのみ学習し、包括的な知識を欠いています。したがって、批判モデルとしてLLMを使用することは理にかなっています。
  • クリティカルモデルのサイズ選択。Self -RAGは既に7Bおよび13Bモデルでテストされており、優れた結果が得られています。しかし、3Bなどのより小さなLLMに切り替えた場合、どのような違いが見られるでしょうか?同様に、33Bなどのより大きなLLMに移行した場合、システムパフォーマンスはどの程度向上するでしょうか?
  • 人間のフィードバックに基づく強化学習(RLHF)をなぜ使わないのでしょうか?本論文では、タスク固有のサンプルデータを用いてターゲット言語モデルを学習させることを提案しています。このデータは、オフラインでありながら重要なモデル(リフレクショントークンを使用)を用いて拡張され、RLHFと比較して学習コストを大幅に削減します。さらに、自己RAGにおけるリフレクショントークンは推論フェーズにおける制御可能なコンテンツ生成を可能にしますが、RLHFは学習中に人間の好みに合わせることに重点を置いています。しかしながら、本論文にはRLHFに関する比較実験は含まれていません。

06 結論

この記事は、身近な実体験シナリオ(オープンブック試験)から始まり、セルフRAG技術の基本プロセスとコード解説を紹介します。また、著者の洞察と考察もいくつか紹介します。

RAG (Retrieval Enhancement Generation) テクノロジーに非常に興味がある場合は、このシリーズの他の記事を自由に閲覧して共有してください。 :)

読んでくれてありがとう!

ジュリアン・イップ

マルチクラウド データ アーキテクト | Azure、GCP、Databricks 認定 | ML および MLOps 実践者

終わり

参考文献

[1] https://medium.com/ai-in-plain-english/a-brief-introduction-to-retrieval-augmented-generation-rag-b7eb70982891

[2] https://arxiv.org/pdf/2310.11511.pdf

[3] https://github.com/AkariAsai/self-rag

[4] https://github.com/langchain-ai/langgraph/blob/main/examples/rag/langgraph_self_rag.ipynb?ref=blog.langchain.dev

[5] https://github.com/run-llama/llama_index/tree/v0.10.20/llama-index-packs/llama-index-packs-self-rag

[6] https://github.com/run-llama/llama_index/blob/v0.10.20/llama-index-packs/llama-index-packs-self-rag/llama_index/packs/self_rag/base.py

[7] https://github.com/run-llama/llama_index/blob/v0.10.20/llama-index-packs/llama-index-packs-self-rag/llama_index/packs/self_rag/base.py#L174

[8] https://github.com/run-llama/llama_index/blob/v0.10.20/llama-index-packs/llama-index-packs-self-rag/llama_index/packs/self_rag/base.py#L245

この記事は、原著者の許可を得てBaihai IDPによって翻訳されました。翻訳の転載をご希望の場合は、お問い合わせください。

オリジナルリンク:

https://ai.gopubby.com/advanced-rag-08-self-rag-c0c5b5952e0e