レバレジーズ データAIブログ

インハウスデータ組織のあたまのなか

LLMの回答精度を上げる「RAG」とは? 処理フローと「ベクトル検索」の役割について調べてみた

はじめに

こんにちは!レバレジーズのテクノロジー戦略室でデータエンジニアをしている佐々木です。

みなさん業務にAIを使っていますか?私はNotebookLMをよく使っています。
自分が指定した情報源から回答を生成してくれるので、信頼度が高いのがありがたいです。

レバレジーズでは、社内で使われている独自の用語を質問できるNotebookLMも提供されており、入社直後の先輩社員が話している言葉がわからない問題の強い味方です!

NotebookLMのように、外部情報を読み込ませてAIをカスタマイズする技術を「RAG」といいます。
いつもお世話になっているのですが、「RAGって何?」と聞かれるとちゃんと説明できないなと感じたので、改めてその仕組みを調べてみました。

RAGとは?

RAGは、LLM(大規模言語モデル)が、外部のデータベースから質問に対する関連情報を検索し、その知識を使って回答を生成する技術です。
LLMの課題の一つに、学習していない最新情報や社内独自の非公開情報などを回答できない(or 誤った情報を回答してしまう)というものがあります。
RAGはそういった課題の解決策の一つで、ユーザーが追加のデータを渡すことで、LLMはその内容を踏まえた回答ができるようになります。

処理の流れは以下の通りです

  • 検索(Retrieval):外部のデータから、質問文に関連する情報を取得(※後述)
  • 拡張(Augmented):検索結果とユーザからの質問文を一緒にLLMに渡す。その際に外部データで取得した情報を優先的に使用する
  • 生成(Generation):質問文に対する回答を生成する

「関連する情報」はどうやって見つける?

RAGの検索(Retrieval)フェーズで行っている「この質問はこの情報と関連しそうだな」という情報はどうやって見つけているのでしょうか。

一般的に使われるキーワード検索では、単純に検索に使用した文字列がデータに含まれる文字列と一致するかどうかを判断します。
一方RAGで使われるのは「ベクトル検索」や「セマンティック検索(意味検索)」です。
これは単語の表面的な文字列だけではなく、「意味」や「文脈」が近いかどうかで関連情報を抜き出す技術です。
今回は例としてベクトル検索の考え方を紹介します。

ベクトルに変換して意味を座標にする
データをベクトルに変換することで多次元空間上の「座標」で文章同士を比較することができるようになります。
この空間では、同じ文脈の中で登場するような意味が近い単語や文は、その座標も近くなります。

  • 文A: 「AIの未来について知りたい」
  • 文B: 「人工知能の将来性に関するレポート」
  • 文C: 「今日の天気は晴れです」

これらをベクトル化すると、文Aと文Bは非常に近い座標を持ちますが、文Cはまったく異なる遠い場所のベクトルを持ちます。

類似度を計算する
検索では、「質問のベクトル」と「データのベクトル」がどれくらい似ているかを計算します。この時よく使われるのが「コサイン類似度」です。

コサイン類似度では、2つのベクトルの「角度」に注目します。

  • 角度が小さい(向きが同じに近い): 意味が似ている
  • 角度が大きい(向きが異なる): 意味が似ていない(類似度が低い)

ベクトルの「長さ(文章の長さ)」に影響されにくく、「方向(文章の内容)」の類似性を測るのに適しています。

簡単な単語で、コサイン類似度の計算を試してみます。
(単語のベクトル化の部分はSentenceTransformerを使って事前学習済みのモデルでベクトル化しました。)

■コード

from sentence_transformers import SentenceTransformer, util


# 日本語対応の軽量モデルをロード
model = SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2')


sentences = [
   "鳥",          # 検索クエリ
   "トマト",        # 単語A
   "かめ"           # 単語B
]


# ベクトル化
embeddings = model.encode(sentences, convert_to_tensor=True)


# コサイン類似度を計算
score_ab = util.cos_sim(embeddings[0], embeddings[1]).item()
score_ac = util.cos_sim(embeddings[0], embeddings[2]).item()


# 結果を出力
print(f"基準文: {sentences[0]}\n")
print(f"「{sentences[1]}」との類似度: {score_ab:.4f}")
print(f"「{sentences[2]}」との類似度:      {score_ac:.4f}")

■結果

基準文: 鳥

「トマト」との類似度: 0.4067
「かめ」との類似度:      0.7305

イメージしやすいようにそれぞれのベクトルの次元を削減して2次元のグラフにプロットすると以下のようになります。

※ベクトル同士の「角度(θ)」が0°に近いほど「コサイン類似度」が1.0に近づき、似ていることを示します。「鳥」と「かめ」はベクトルの長さは異なりますがベクトル同士の角度が近いため「似ている」と捉えます。「トマト」と「かめ」はベクトルの長さは大体同じですがベクトル同士の角度が大きいため、類似度は低くなります。

このようにして文章をベクトル化して類似した意味を持つデータをピックアップしています。

「ベクトル検索」自体は、実は新しい技術ではなく、例えばECサイトの「この商品を買った人はこれも見ています(レコメンド)」や、Googleの画像検索などでも、類似度を計算する同様の技術が使われています。
ベクトル検索含む様々な検索で言葉の類似性を計測し、LLMと組み合わせることで便利になったのが今のRAGなんですね。

まとめ

今回調べてみて、ふんわりしていたRAGの理解度が少しだけ上がりました!
勉強しながら書いた記事ですので、もし不正確な記述があれば教えていただけると嬉しいです〜!