はじめに
こんにちは、慶應義塾大学理工学部機械工学科3年の金賢佑です!
私は東大発AIベンチャーの株式会社2WINSの元でインターンをしています!
これからの記事では自分がインターンを通して最強のAIエンジニアを目指していきます!
第七回はRAGの基礎を記録しました!
RAGとは
RAGとは簡単にいうとLLMに答えさせる前に、知識を検索してくれる仕組みのこと!
LLMはあくまで「学習済みの知識」しか知りません!
なので例えば、
👨「会社のマニュアルをもとに質問に答えてほしいな....」と思っても
🤖「何そのマニュアル...知らないんだけど...」
となってしまいます!
RAGでは何ができるの?
今回はRAGでは主要なコンポーネントとして5つ紹介します!
Document loader
:PDFなどの文書を読み込む
Document transformer
:読み込んだ文書を使いやすいサイズに分割!例えば一万字のPDFを500文字ごとに分ける!(この単位をチャンクって言うよ)
チャンクに直すことによって
①大量のテキストデータを小分けにして処理することが可能になり、検索効率が向上する
②Embedding
のトークン制限を回避する
などの利点が挙げられます。
なお、適切なチャンクサイズは200~500トークンと言われていて、小さすぎると意味のなくなりがなくなり、大きすぎると検索をかける際に無関係な文もヒットするなどのデメリットがあります!
Embedding Model
:チャンクをベクトルに変換する!
例えば、「この製品は高い耐久性と防水性を備えており、アウトドアでも安心して使えます。」みたいな文章があった時、
①["この", "製品", "は", ......]のようにテキストがトークン化される
②損失関数などを用いた事前学習済みの埋め込み行列から、それぞれにベクトルが渡される(ベクトルの数値が似ているものほど、似た意味を持っている)
③各トークンの平均or特定のトークンでまとめて一つのベクトルに集約される
④文全体の意味のベクトルが完成する
このことについてはday3でも紹介したこちらの動画をご覧ください!(chapter7くらいまであります)
https://youtu.be/tc8RTtwvd5U?si=ynBpv-bFHHjfwOCz
Vector store
:ベクトル化したチャンクを保存する!
こうしてできたベクトルのデータベースは普通のデータベースと違い、似た文を探す、いわゆる「曖昧な検索」が可能になります。
とっても便利!
Retriever
:ユーザーの質問に応じてVector store
から情報を検索する!
仕組みとしては例えば
①ユーザーが質問する👦「地球の直径はどれくらい?」
②質問文をベクトルに変換!これはさっきやったのと同じ!
③あらかじめEmbedding
済みのデータベースに保存されているベクトル群の中で、上記のベクトルと似ているものを抽出!距離が近ければ意味も似ているので、ユークリッド距離を使ったり、ドット積を使います!
ドット積の結果の見方は以下を参考にしてください!
ちなみに補足ですが、Retriever
だけだと精度に問題があることがあります。間違った情報を渡してしまうことをハルシネーションと言います。
こうしたリスクを回避する方法として、Retriever
が意味的に近いだろうと持ってきたチャンクを再評価してくれるReranker
を間に挟むこともあります
Reranker
は例えば、Retriever
が持ってきた10個のチャンクをスコア付けし、最も高いものを選んでくれます!
では実際に一つずつ使ってみましょう!
Document loader
ではまず初めにDocument loader
を使ってみよう!
以下のコードを打ってください。
!pip install langchain-community==0.3.0 GitPython==3.1.43
from langchain_community.document_loaders import GitLoader
def file_filter(file_path: str) -> bool:
return file_path.endswith('.mdx')
loader = GitLoader(
clone_url='https://github.com/langchain-ai/langchain',
repo_path='./langchain',
branch='master',
file_filter=file_filter,
)
raw_docs = loader.load()
print(len(raw_docs))
こちらのコードではLangChainの公式GitHubリポジトリから.mdx
ファイルだけを抽出して読み込んでいます!
最後にデータ件数を表示してみましたが現在では414件あるみたいですね。
Document loader
のコードは複雑ですね....
一応解読すると、
def file_filter(file_path: str) -> bool:
def file_filter
で関数名を定義。
file_path
と言う名前の引数を受け取るよ言う意味。str
なので引数は文字列。
-> bool
この関数は真偽を返す(True or False)と言う意味!
clone_url='https://github.com/langchain-ai/langchain',
repo_path='./langchain',
branch='master',
file_filter=file_filter,
clone_url='https://github.com/langchain-ai/langchain'
でGitHubからのリポジトリをクローン
repo_path='./langchain'
でローカルの保存先を指定
branch='master'
でブランチを指定
file_filter=file_filter
で上で定義したオブジェクトを引数に渡している!
Document transformer
続いてはドキュメントをチャンクで分割してみましょう!
!pip install langchain-text-splitters==0.3.0
まずは必要なものをインポートしてください!
その後テキストをチャンクに分割していきます!
from langchain_text_splitters import CharacterTextSplitter
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
docs = text_splitter.split_documents(raw_docs)
print(len(docs))
chunk_size
っていうのは一つの塊であるチャンクに含める最大文字数(またはトークンの数)のこと!
出力でWARNING:langchain_text_splitters.base:Created a chunk of size 1281, which is longer than the specified 1000
っていう警告が出ているのはchunk_size=1000
では分割しきれなかったよ!っていうことです。
かといってchunk_size
をあげすぎても分割できる量が少なくなってしまうから難しい...
Embedding model
次はベクトル化を行ってみます。
from langchain_openai import OpenAIEmbeddings
embeddings = OpenAIEmbeddings(model='text-embedding-3-small')
query = 'LangChainでPDFを読み込むにはどうしたらいいですか?'
vector = embeddings.embed_query(query)
print(len(vector))
print(vector[:3])
これを実行すると、下のように出力されます。
今回は長いので3要素しか出力していませんが、実際は1536次元のベクトルが与えられています!(気になった人は[:3]を消してみてください!)
1536
[-0.011487526819109917, -0.0013641082914546132, 0.013325303792953491]
Vector store & Retriever
続いてベクトル化したドキュメントの保存を行ってみます。
最初にGithubからコピーしてきたデータを処理しています!
!pip install langchain-chroma==0.1.4
続いてChroma
をインポートしてみます!
from langchain_chroma import Chroma
ob = Chroma.from_documents(docs, embeddings)
Chroma
はLangChainで使える「ベクトルデータベース」の一つ!
デフォルトではローカル(自分のパソコン)に保存されるよ!
.from_documents()
はテキストを1つずつ取り出してembedding
を使ってベクトル化したのちにChroma
に保存してくれるショートカット関数!
「ショートカット関数」とは一連の処理を一行でやってくれる便利な関数のこと!
Retriever
最後にユーザーの質問に対して関連する情報を取得するRetriever
を使ってみましょう!
query = 'LangChainでPDFを読み込むにはどうしたらいいですか?'
context_docs = retriever.invoke(query)
print(len(context_docs))
first_doc = context_docs[0]
print(first_doc.metadata)
print(first_doc.page_content)
first_doc.metadata
にはファイル情報が含まれていて、出典に該当します。
first_doc.page_content
には実際の文章の中身が入っています。これがLLMに渡す情報になります。
まとめ
今回はRAGの主要なコンポーネントについて紹介しました!
なかなか複雑な文法もあったと思いますが、流れだけでも理解できたら十分だと思います!
お疲れ様でした!
参考文献
LangChainとLangGraphによるRAG・AIエージェント〈実践〉入門/西見公宏/吉田真吾/大嶋勇樹