3847 Words 17 min

はじめに

今回は今時もはや定番となっているLLM(chatGPTなど)をシステムに組み込むにあたり、RAGについて調べた。

その際に「LLMのファインチューニングとRAG(チャットボット開発による実践)」著:新納浩幸を読んだ。

読んだ際のメモを記事として残す。

この記事ではRAGについてのことしか書かないが、本ではFine TuningやローカルでLLMを構築する手順なども詳しく述べられている。 また、実際に構築するためのコード等も書かれていたが、それを記事に載せてしまうのは良くないのでこの記事では載せない。 読みやすくてためになる良い本だったので、興味があればぜひご一読を。 https://www.ohmsha.co.jp/book/9784274231957/

RAGの目的

例えば、自社サービスに関連する質問に回答するチャットbotを作りたい時など

一般的な知識ではなくビジネス固有の知識を与えたLLMを使いたい時。 選択肢はFine TuningとRAGの二つの選択肢がある。

Fine Tuningでは、学習させるデータの準備や学習するためのマシンリソースが必要である上、一般的な知識を完全に上書きするのが難しい。

それらの問題を解決できるのがRAGである。

RAGの仕組み(概要)

1行で言うなら、 「ユーザーからの質問に、独自情報を付け足してLLMに渡すという仕組み」がRAGだ。

もう少し詳細にすると以下のフロー

  1. 質問文をもとに、独自データベースに検索をかける
  2. データベースから返ってきた関連情報と元の質問文を適切に合体させる
  3. 合体させたものをLLMに投げる

図にするとこう

graph TD;
    質問文 --> 独自データベース;
    質問文 --> リクエスト本体;
    独自データベース --> リクエスト本体;
    リクエスト本体["リクエスト本体\n(関連データ + 質問文)"] --> LLM;

データベースを作る流れ

一言でデータベースの仕組みといってもかなりの種類がある。

それはデータベースの仕組み自体にも種類があるし、独自の知識の形式(例: PDFファイル、表、テキスト)にも種類があるからである。

今回はテキスト形式の知識に対して、FAISS(近似最近傍検索ライブラリ)を使ったデータベースの構築方法を説明する。

以下でステップに分けて説明する

1. 文章をパッセージに分割

文章を例えば、一文ごとや文節ごと、固定の長さごとなどの単位に分割する。

この時にどの単位で分割するかは、要件によって異なるが、今回は単純に100文字ごとに分割することを考える。

この時にoverlap(被り)を設定することもあり、overlapの値によってはデータ量が増える。

イメージとしては以下の感じだ

元の文章
「吾輩は猫である」

上記文章を4文字ずつのパッセージに分け、overlap(被り)を1文字とする。

パッセージ1
「吾輩は猫」
パッセージ2
「猫である」

この作業を行えるツールは転がっているので、適当に選択すれば良い。 本ではlangchainのtext_splitterというライブラリが紹介されていた。

2. パッセージ→ベクトルへの変換

パッセージを特殊な処理でベクトル座標に変換する(embedding)。

この時に、類似性や関連性が高いパッセージは近いベクトルに置かれることとなる。 つまり、似たような文章や、一緒に使われる可能性の高い文章は、ベクトル座標上でも近い位置に置かれるのだ。

実はこの処理自体もAIのモデルを使って行う。

本書では無料で使えて日本語に対応しているものとして、intfloatのmultilingual-e5-largeが紹介されていた。 (著者の体感によると、クオリティ的には有料のopen ai のembeddingsと同じレベルらしい)

embeddingsという概念を理解するのが難しい。。。

文章という情報量の多いものを抽象化して、機械でも扱える情報量の少ない形にしている。

少し自分で調べてみた。 例で考えよう。

embeddingのイメージ

単語をベクトルに変換(ebmedding)したいとする。

「漫画」「コミック」「アニメ」「映画」「カラス」という言葉をベクトルに変換するとしたら、例えばこのような形になる。

漫画とコミックは類似しているので近くに置かれ、漫画やコミックと同じ文脈で使われることの多いアニメも近い座標に置かれる。

一方で、それぞれとの類似性や関連性の薄いカラスは遠くに置かれる。

このようにそれぞれのパッセージをベクトルに変換して、座標空間上におくことで関係性をわかりやすくする処理を「ebmeddings」と呼ぶ。

3. データベース化

ベクトルに変換したデータがいっぱいあってもそれだけでは使えるデータとはならない。 与えられた質問文と関連するデータを検索して持ってくる必要があるからだ。

普通だとベクトルデータの検索には最近傍検索というものを使う。 しかし、実は愚直に最近傍検索を行うと遅すぎるので何かしらの工夫をする必要がある。

その例がMETA社のFAISS(近似最近傍検索ライブラリ)を使うというものであったり、Pineconeのライブラリを使うというものである。

基本的にデータベースは、検索方法に最適化されてものである必要があり、一から作るのは現実的ではないのでどのツールを選ぶか決めれば良い。

以上でデータベースを作る流れは終了だ。 このデータベースをLLMと繋げればRAGの構築は完成。

LLMとデータベースを繋げる

データベースでの検索結果を保持して、それをLLMに投げるリクエストに加えるのが最も単純だが 実装するとなると手間がかかるので、langchainのretrieval QAを使うことがお勧めされていた。

OpenAIのLLMを使うならプロンプトにデータベースからの検索結果を埋め込むところも自動でやってくれるらしい。

本書では自前のLLMでやる方法や、langchainとopenAI以外のLLMを組み合わせる方法も紹介されていたがここでは割愛する。

RAGの性能について

RAGの性能を上げたい時に考慮するべき要素を大別すると、「モデル」「データ」「検索手法」の三つだ。

モデル

当たり前だが高性能なモデルのLLMを使った方がRAGの性能は高くなる。

OSSのモデルもたくさん公開されているが、chatGPTのようにサービスとして提供されているものを利用した方が精度は高い。

モデルの性能の比較は非常に難しいので、実際に使ってみてクセを理解しつつ最適なものを選択するべきだ。

ただし一つ確実に需要になってくる指標としてcontext windowがある。 context windowは一度に受け取れる情報量のことだ。

これが大きければ大きいほど一度に大量のテキストを読み込ませることができる。 これはmodelの性能に比例している上に、直接的に良い効果も持っている。

context windowが大きいと無関係な文書の影響が少なくなる ↓ データベース検索上位N位のNの数字を大きくすることができる ↓ 正解が含まれやすくなる

データ

  1. 不要なものの削除
  2. チャンクの最適化

の二つを行なってあげる必要がある。

1 不要なものの削除

できるだけplaneな文書が良いので、コツコツ前処理を行なって最適化してあげる必要がある。

例えばHTMLのタグや罫線などは普通の文書ではないのでノイズになる。

これを削除していくことで無駄な情報が減って、LLMの回答文書の精度が上がるとともに 検索されたチャンクの情報量が濃くなってくるので、検索に正解が含まれる可能性も上がる

2 チャンクの最適化

これはバランスを見て行なってあげる必要がある。

チャンクの長さが長すぎても、短すぎても問題がある。

チャンクが長すぎると、正解の文書が含まれる可能性が上がるが、LLMに渡す情報量が増えて回答の精度が下がっていく。

一方でチャンクが短すぎると、正解の文書が含まれる可能性が下がるし、チャンクの途中で文書が途切れて回答が正しい文章にならない可能性が出てくる。

検索手法の工夫

今まで解説してきた検索手法は、質問文と文書をベクトル化して、ベクトルが近いものを検索するという「ベクトルベースの検索」だった。

しかし、質問文と文書をキーワードの集合に変換して、キーワードが一致するものを検索する「キーワードベースの検索」が有効な場合もある。 どころか、昔からあるキーワードベースの検索は割と安定しているので試す価値は高そうだ。

本書ではLangchainのBM25Retrieverが紹介されていた。

またベクトルベースの検索でもいろいろな手法が生み出されている。 その例の一つがHyDEだ。

HyDE: Hypothesical Documnt Embeddings

従来の手法はデータベースから検索をかけるときに、元の質問と最も関連性が高いものを検索していた。 しかし、HyDEでは、少し違う。

まず質問文から仮想の回答文書を作成し、その仮想回答文書と関連性が高い文書を検索する。 そして質問文と文書をRAGに渡すという手法だ。

感想まとめ

今回は仕事で必要になってきたためRAGについて調べた。 本書でも書いてあるように、RAGは今後のLLMの使い方の中心になってくる可能性が高い。

Fine Tuningするほどのデータが溜まっていないが、専門知識を持ったLLMを使いたいという場面はかなり多いだろうと思う。

しっかり仕組みから理解することで、最適な設計でRAGを組めるようになる。 また、精度をあげる上でどこがボトルネックになっているかを理解することで、最適な改善策を見つけることができるだろう。

本書は非常に良い入門書であり、良い一歩を踏み出せたので、もう何冊か読んでより理解を深めていこうと思う。