大規模言語モデル(LLM)は膨大な知識を持っていますが、学習データに含まれない最新情報や専門的な社内文書には対応できません。この課題を解決するのが RAG(Retrieval-Augmented Generation: 検索拡張生成) です。
RAGは外部知識ベースから関連情報を検索し、それをLLMのプロンプトに組み込むことで、最新かつ正確な回答を生成します。
本記事の内容
- RAGの基本概念とアーキテクチャ
- 埋め込みベクトルと類似度検索の数学的基礎
- PythonでのシンプルなRAGシステムの実装
前提知識
この記事を読む前に、以下の概念を理解しておくと役立ちます。
- ベクトルの内積とコサイン類似度
- Transformerの基本的な仕組み
RAGとは
RAG(Retrieval-Augmented Generation)は、2020年にMeta AI(旧Facebook AI Research)が提案した手法で、検索(Retrieval)と生成(Generation)を組み合わせたアーキテクチャです。
従来のLLMは学習時に獲得した知識のみを使って回答を生成します。これに対しRAGは、質問に関連する外部文書を検索し、その内容をコンテキストとしてLLMに渡すことで、より正確で最新の情報に基づいた回答を可能にします。
RAGのアーキテクチャ
RAGシステムは以下の3つの主要コンポーネントで構成されます。
- インデクシング(Indexing): 文書を埋め込みベクトルに変換して保存
- 検索(Retrieval): クエリに類似した文書を検索
- 生成(Generation): 検索結果をコンテキストとしてLLMで回答生成
処理フローは以下のようになります。
$$ \text{Query} \xrightarrow{\text{Embedding}} \bm{q} \xrightarrow{\text{Search}} \{D_1, D_2, \ldots, D_k\} \xrightarrow{\text{Augment}} \text{Prompt} \xrightarrow{\text{LLM}} \text{Answer} $$
埋め込みベクトルと類似度検索
RAGの核心は、テキストを意味的に類似したベクトルに変換し、効率的に検索することです。
埋め込みベクトル
テキスト $t$ を $d$ 次元のベクトル空間に写像する関数を埋め込み関数 $E$ とします。
$$ E: \mathcal{T} \to \mathbb{R}^d $$
ここで $\mathcal{T}$ はテキストの集合です。意味的に類似したテキストは、ベクトル空間上で近い位置に配置されます。
コサイン類似度
2つのベクトル $\bm{a}$ と $\bm{b}$ の類似度を測る指標として、コサイン類似度がよく使われます。
$$ \text{sim}(\bm{a}, \bm{b}) = \cos\theta = \frac{\bm{a} \cdot \bm{b}}{|\bm{a}||\bm{b}|} = \frac{\sum_{i=1}^{d} a_i b_i}{\sqrt{\sum_{i=1}^{d} a_i^2} \sqrt{\sum_{i=1}^{d} b_i^2}} $$
コサイン類似度は $-1$ から $1$ の範囲を取り、$1$ に近いほど類似していることを示します。
Top-k検索
クエリベクトル $\bm{q}$ に対し、文書集合 $\mathcal{D} = \{D_1, D_2, \ldots, D_n\}$ から最も類似度の高い $k$ 件を取得します。
$$ \text{TopK}(\bm{q}, \mathcal{D}, k) = \underset{S \subseteq \mathcal{D}, |S|=k}{\arg\max} \sum_{D \in S} \text{sim}(\bm{q}, E(D)) $$
プロンプト拡張
検索で得られた文書 $\{D_1, \ldots, D_k\}$ をコンテキストとしてプロンプトに組み込みます。
$$ \text{Prompt} = \text{Template}(\text{Context}, \text{Query}) $$
典型的なテンプレートは以下のような形式です。
以下のコンテキストを参考に質問に答えてください。
コンテキスト:
{context}
質問: {query}
回答:
Pythonでの実装
シンプルなRAGシステムをPythonで実装します。ここではsentence-transformersを使って埋め込みベクトルを生成し、NumPyでコサイン類似度検索を行います。
import numpy as np
from sentence_transformers import SentenceTransformer
# 埋め込みモデルの読み込み
model = SentenceTransformer('all-MiniLM-L6-v2')
# サンプル文書(知識ベース)
documents = [
"RAGは検索と生成を組み合わせた手法です。",
"Transformerは自己注意機構を使ったニューラルネットワークです。",
"LLMは大規模言語モデルの略称です。",
"ベクトルデータベースは埋め込みベクトルを効率的に検索します。",
"プロンプトエンジニアリングはLLMの出力を制御する技術です。",
]
# 文書を埋め込みベクトルに変換
doc_embeddings = model.encode(documents)
def cosine_similarity(a, b):
"""コサイン類似度を計算"""
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
def retrieve(query, k=2):
"""クエリに類似した文書を検索"""
query_embedding = model.encode(query)
# 各文書との類似度を計算
similarities = [
cosine_similarity(query_embedding, doc_emb)
for doc_emb in doc_embeddings
]
# 類似度の高い順にソート
ranked_indices = np.argsort(similarities)[::-1]
# Top-k文書を返す
results = []
for i in ranked_indices[:k]:
results.append({
'document': documents[i],
'similarity': similarities[i]
})
return results
# 検索テスト
query = "RAGとは何ですか?"
results = retrieve(query, k=2)
print(f"クエリ: {query}\n")
print("検索結果:")
for i, r in enumerate(results, 1):
print(f" {i}. {r['document']} (類似度: {r['similarity']:.4f})")
実行結果の例:
クエリ: RAGとは何ですか?
検索結果:
1. RAGは検索と生成を組み合わせた手法です。 (類似度: 0.7234)
2. LLMは大規模言語モデルの略称です。 (類似度: 0.4521)
プロンプト生成と回答
検索結果をコンテキストとしてプロンプトを生成します。
def generate_prompt(query, retrieved_docs):
"""検索結果を含むプロンプトを生成"""
context = "\n".join([doc['document'] for doc in retrieved_docs])
prompt = f"""以下のコンテキストを参考に質問に答えてください。
コンテキスト:
{context}
質問: {query}
回答:"""
return prompt
# プロンプト生成
query = "RAGとは何ですか?"
results = retrieve(query, k=2)
prompt = generate_prompt(query, results)
print(prompt)
実際のRAGシステムでは、このプロンプトをOpenAI API等のLLMに渡して回答を生成します。
RAGパイプライン全体の実装
完全なRAGパイプラインをクラスとしてまとめます。
import numpy as np
from sentence_transformers import SentenceTransformer
class SimpleRAG:
def __init__(self, model_name='all-MiniLM-L6-v2'):
"""RAGシステムの初期化"""
self.model = SentenceTransformer(model_name)
self.documents = []
self.embeddings = None
def add_documents(self, documents):
"""文書をインデックスに追加"""
self.documents.extend(documents)
self.embeddings = self.model.encode(self.documents)
def retrieve(self, query, k=3):
"""類似文書を検索"""
query_emb = self.model.encode(query)
# コサイン類似度を一括計算
similarities = np.dot(self.embeddings, query_emb) / (
np.linalg.norm(self.embeddings, axis=1) * np.linalg.norm(query_emb)
)
# Top-kインデックスを取得
top_k_idx = np.argsort(similarities)[::-1][:k]
return [
{'document': self.documents[i], 'score': similarities[i]}
for i in top_k_idx
]
def generate_prompt(self, query, k=3):
"""RAGプロンプトを生成"""
results = self.retrieve(query, k)
context = "\n".join([f"- {r['document']}" for r in results])
return f"""以下の情報を参考に質問に答えてください。
参考情報:
{context}
質問: {query}
回答:"""
# 使用例
rag = SimpleRAG()
rag.add_documents([
"RAGはRetrieval-Augmented Generationの略で、検索拡張生成と訳されます。",
"RAGは2020年にMeta AIによって提案されました。",
"RAGはLLMの幻覚(ハルシネーション)を軽減する効果があります。",
"ベクトル検索は埋め込み空間での最近傍探索を行います。",
])
prompt = rag.generate_prompt("RAGの利点は何ですか?")
print(prompt)
RAGの評価指標
RAGシステムの性能を評価する主要な指標を紹介します。
検索精度
検索コンポーネントの評価には、以下の指標を使用します。
Recall@k(再現率): 正解文書が上位 $k$ 件に含まれる割合
$$ \text{Recall@}k = \frac{|\text{Relevant} \cap \text{TopK}|}{|\text{Relevant}|} $$
MRR(Mean Reciprocal Rank): 正解文書の順位の逆数の平均
$$ \text{MRR} = \frac{1}{|Q|} \sum_{i=1}^{|Q|} \frac{1}{\text{rank}_i} $$
生成品質
生成コンポーネントの評価には以下を使用します。
- Faithfulness(忠実性): 回答がコンテキストに基づいているか
- Answer Relevance(回答関連性): 回答が質問に適切に答えているか
まとめ
本記事では、RAG(検索拡張生成)の基本概念と実装方法を解説しました。
- RAGは検索(Retrieval)と生成(Generation)を組み合わせた手法
- 埋め込みベクトルとコサイン類似度による類似文書検索が核心
- 検索結果をコンテキストとしてLLMに渡すことで、正確な回答を生成
次のステップとして、以下の記事も参考にしてください。