著者 | フロリアン・ジューン 編纂者:岳陽 この記事は、よくある人生のシナリオ、つまりオープンブック試験の受験から始まります。私たちは通常、以下の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 つのステップで構成されます。
上記のモデルは特別にトレーニングされており、そのトレーニング プロセスについてはこの記事の後半で説明することに注意してください。 02 リフレクショントークンの紹介図 2 に示すように、自己 RAG フレームワークは、生成プロセス中にリフレクション トークンを使用してより正確な制御を行う点で RAG と異なります。 図2:Self-RAGで使用される4種類の反射トークン。各タイプは複数のトークンを用いて出力値を表す。下3行はクリティカルトークンの3つのカテゴリを示し、太字で示されているのは各カテゴリで最も理想的なクリティカルトークンである。x、y、dはそれぞれ入力、出力、関連箇所を表す。出典:Self-RAG[2] 一般的に、自己 RAG は次の 4 つの異なる判断を行います。
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技術の強化と最適化のための新たな視点を提供します。しかしながら、この手法はより複雑な学習プロセスを必要とし、生成フェーズに特別なメカニズムを組み込んでいます。対象の出力テキストだけでなく、様々な種類のフィードバックラベルも生成し、生成中にこれらのラベルに基づいて複数の決定操作を実行します。これにより推論コストが必然的に増加し、リアルタイム性能を優先するプロジェクトに深刻な影響を与える可能性があります。 さらに、このフレームワークには大きな改善の余地があります。さらなる議論とイノベーションを促すために、いくつかの提案をさせていただきます。
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 |