2024/08/11
RAGを使う(2)
前回、Interface誌の8月号に記載の『小説「注文の多い料理店」でRAGとLLMを比べる』の内容に従ってPythonコードを動かしてみると、精度と処理速度(処理時間)で「もやもや感」が残りました。精度面では前回1.3b(13億)パラメータのFreeの学習データ(llm-jp-1.3b-v1.0)を利用したので、パラメータ数の多いモデルならば精度向上が期待できます。ただし、処理時間が長くなるのを覚悟する必要があります。
また、処理速度面では、OpenAIやAzureOpenAIのようなGPUが前提のクラウド環境で実行すれば改善できそうです。以前試してみたLlamaIndexでユーザ個人の情報をChatGPTに付与するのも、まさにRAGですね。
以上の背景から、以下の5点についてRAGの検証をしてみようと思いました。
1. 前回と同じプログラムで13bのLLMモデル
2. OpenAI
3. AzureOpenAI
4. LlamaIndex
(5. Azure AI Search)
この中で5番目のAzure AI Searchは、やりかけていたのですが、うまく行っていません。学習させる際にAzure上でVirtual Machine(サーバ)を立てなくてはならず、かつ学習時間も1時間以上の所で、先が見えない不安がよぎり止めました。結局、1時間で700円程度の使用料金を取られて成果もなしです・・。もう少し調査・研究が必要と痛感しました。1〜4は検証できたので、順にその内容をお話しします。
先に結果と結論を言うと以下の表の通りです。プログラムの各プロセスにおいて、どの程度処理時間がかかっているかについても調べました。2. OpenAI、3. AzureOpenAI、4. LlamaIndexのみ使えそうで、1. Freeモデル、5. Azure AI Searchは現時点で使えないことが分かりました。
1. 前回と同じプログラムで13bのLLMモデル
前回と同じプログラムで、Embeddingはそのままで、LLMのモデルのみを13b(130億)パラメータ(llm-jp-13b-v1.0)に変更しました。予想通り、回答を終えるまでの実行時間は577.1min(9h37min)という、1.3bモデルに比べて約14倍の時間がかかりました。
RAGを使った場合の回答結果です。doc1とdoc2の内容は1.3bモデルと全く同じでしたが「answer: /*」となりました。回答できないと言うことでしょうか?時間がかかった割には残念な結果です。
question: 料理店の札には何という店名が書かれていましたか次に、LLM単独の場合です。結果は「/*」で、これも「スカ」でした。パラメータ数を多くしても報われず、使えないと判断しました。
answer: /*
doc1: 「喰《た》べたいもんだなあ」
二人の紳士は、ざわざわ鳴るすすきの中で、こんなことを云いました。
その時ふとうしろを見ますと、立派な一軒《いっけん》の西洋造りの家がありました。
そして玄関《げんかん》には
[#ここから4字下げ、横書き、中央揃え、罫囲み]
RESTAURANT
西洋料理店
WILDCAT HOUSE
山猫軒
[#ここで字下げ終わり]
という札がでていました。
doc2: [#ここで字下げ終わり]
と書いてありました。扉のすぐ横には黒塗りの立派な金庫も、ちゃんと口を開けて置いてありました。鍵《かぎ》まで添《そ》えてあったのです。
「ははあ、何かの料理に電気をつかうと見えるね。金気《かなけ》のものはあぶない。ことに尖ったものはあぶないと斯《こ》う云うんだろう。」
「そうだろう。して見ると勘定《かんじょう》は帰りにここで払《はら》うのだろうか。」
2. OpenAI
プログラムは、前回と今回の1.で実行したプログラムと同じ流れで、OpenAIのLLMモデルの「gpt-4o-mini」、Embeddingには「text-embedding-ada-002」を使いました。要所要所で時間計測が入っており、見にくいですが、実行プログラムは以下の通りです。
import timeRAGを使った場合の回答結果です。正解を導くことができています。
start_time1 = time.time()
import re
import os
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough, RunnableParallel
from langchain_core.prompts import PromptTemplate
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import FAISS
from langchain_openai import ChatOpenAI
from langchain_openai import OpenAIEmbeddings
end_time1 = time.time()
start_time2 = end_time1
# OpenAI API key setting
os.environ['OPENAI_API_KEY'] = "My key"
# Read a text file
file_path = "./chumonno_oi_ryoriten.txt"
with open(file_path, encoding="shift-jis") as f:
original_text = f.read()
#print(f"先頭の文章:\n{original_text[:500]}\n")
#print(f"末尾の文章:\n{original_text[6500:]}\n")
# Edit the text
pattern = r'-----.*?-----\n\n\s*(.*?)\s*\n\n\n\n底本'
match = re.search(pattern, original_text, re.DOTALL)
text = match.group(1)
#print(f"先頭の文章:\n{text[:500]}\n")
#print(f"末尾の文章:\n{text[6000:]}")
# Define the text split
splitter = RecursiveCharacterTextSplitter(chunk_size=200, chunk_overlap=20, length_function=len)
# Execute the text split
doc = splitter.create_documents([text])
#print(type(doc), type(doc[0]))
#print(doc[0])
end_time2 = time.time()
start_time3 = end_time2
# Text embedding and store a vectorstore
# Define the vectorstore
vectorstore = FAISS.from_documents(documents=doc, embedding=OpenAIEmbeddings())
# Save a local storage
vectorstore.save_local(folder_path="./vs", index_name="chumon")
# Load the vectorstore
vectorstore = FAISS.load_local(folder_path="./vs", embeddings=OpenAIEmbeddings(), index_name="chumon", allow_dangerous_deserialization=True)
retriever = vectorstore.as_retriever(search_type="similarity", search_kwargs={"k": 2})
end_time3 = time.time()
start_time4 = end_time3
# Define Functions
# Define how to combine multiple searched documents
def format_docs(docs):
return "\n\n".join(doc.page_content for doc in docs)
# A function to parse the LLM output and extract only the necessary parts
def return_text_parse(text):
return text.split("\n")[0]
end_time4 = time.time()
start_time5 = end_time4
# Using RAG
prompt = """参考文章を元に最後の質問に回答してください。
下記の例を参考にできるだけ簡潔に回答してください。
質問:今日の天気はなんですか
回答:晴れ
質問:日本の首都はどこですか
回答:東京
参考文章:
{context}
質問:{question}
回答:"""
prompt_template = PromptTemplate.from_template(prompt)
# Set LLM and load
model = ChatOpenAI(model="gpt-4o-mini", temperature=0.0)
parser = StrOutputParser()
# Create a chain of LangChain
rag_chain = (
RunnablePassthrough.assign(context=lambda x: format_docs(x["docs"]))
| prompt_template
| model
| parser
| return_text_parse
)
rag_chain_with_source = RunnableParallel(
{"docs": retriever, "question": RunnablePassthrough()}
).assign(answer=rag_chain)
end_time5 = time.time()
start_time6 = end_time5
# Run
answer = rag_chain_with_source.invoke("料理店の札には何という店名が書かれていましたか")
# Results
print(f"""question: {answer['question']}
answer: {answer['answer']}
doc1: {answer['docs'][0].page_content}
doc2: {answer['docs'][1].page_content}""")
end_time6 = time.time()
start_time7 = end_time6
# Only LLM (no using RAG)
prompt2 = """最後の質問に回答してください。
下記の例を参考にできるだけ簡潔に回答してください。
質問:今日の天気はなんですか
回答:晴れ
質問:日本の首都はどこですか
回答:東京
質問:{question}
回答:"""
prompt_template2 = PromptTemplate.from_template(prompt2)
llm_chain = (
{"question": RunnablePassthrough()}
| prompt_template2
| model
| parser
| return_text_parse
)
end_time7 = time.time()
start_time8 = end_time7
# Run
print(llm_chain.invoke("料理店の札には何という店名が書かれていましたか"))
end_time8 = time.time()
# Time measurement results
print("Execution time1:" + str(end_time1 - start_time1) + "seconds")
print("Execution time2:" + str(end_time2 - start_time2) + "seconds")
print("Execution time3:" + str(end_time3 - start_time3) + "seconds")
print("Execution time4:" + str(end_time4 - start_time4) + "seconds")
print("Execution time5:" + str(end_time5 - start_time5) + "seconds")
print("Execution time6:" + str(end_time6 - start_time6) + "seconds")
print("Execution time7:" + str(end_time7 - start_time7) + "seconds")
print("Execution time8:" + str(end_time8 - start_time8) + "seconds")
question: 料理店の札には何という店名が書かれていましたか次に、LLM単独の場合です。RAGを使っていないので当然学習されていない店の名前でした。
answer: 山猫軒
doc1: 「喰《た》べたいもんだなあ」
二人の紳士は、ざわざわ鳴るすすきの中で、こんなことを云いました。
その時ふとうしろを見ますと、立派な一軒《いっけん》の西洋造りの家がありました。
そして玄関《げんかん》には
[#ここから4字下げ、横書き、中央揃え、罫囲み]
RESTAURANT
西洋料理店
WILDCAT HOUSE
山猫軒
[#ここで字下げ終わり]
という札がでていました。
doc2: 「それあそうだ。見たまえ、東京の大きな料理屋だって大通りにはすくないだろう」
二人は云いながら、その扉をあけました。するとその裏側に、
[#ここから3字下げ]
「注文はずいぶん多いでしょうがどうか一々こらえて下さい。」
[#ここで字下げ終わり]
「これはぜんたいどういうんだ。」ひとりの紳士は顔をしかめました。
「美味屋」OpenAIの「gpt-4o-mini」と「text-embedding-ada-002」を使うことで、処理時間が「4.15sec」という高速で、かつ回答も正確に行うことができました。
長くなりましたので、続きは次回にします。
コメント