概要
社内規定を熟知したチャットモデルが欲しかったので、試しにGoogle Colaboratory上でrinna/japanese-gpt-1bをベースにしたRetrieval QAを作ってみました。
参考
目的・背景
仕事をしていると「この手続きって部長承認要るんだっけ?」「PCを手配するための手続きが分からん」など、ルールが分からなくて困ることがあります。
もちろん社内規定やWikiを漁れば見つかるのですが、そのひと手間が面倒で…。
社内規定を熟知したLLMをローカルで作ってchatGPTみたく簡単に聞けたら便利だよなぁと思い、簡単なものをGoogle Colaboratory上で作ってみることにしました。
大きい企業だと既に導入されてたりするのかな?うらやましい…
手段
もともとLLMのファインチューニングで実現できるのかな、と考えていたのですが、下記の記事によるとRAGという技術をベースにした方が良いそうです。
RAG(Retrieval Augmented Generation)というのは、簡単に言えば
「LLMに質問したときに、(今回で言うところの社内規定などの)情報源から関係のありそうな部分を引っ張ってきてLLMに与えて回答を生成する」というものだそう。
ちなみにRetrieval QAとも呼ばれているそうですが、RAGと同値の意味なのか、それとも区別がある単語なのか分かっていません。良かったらコメント等で教えてほしいです…
実装
下記の記事を参考にさせて戴きました。
上記がほぼそのまま動くのですが、折角なのでモデルを変えてrinna/japanese-gpt-1bで試してみました。
環境の準備
無料枠ではメモリが足らず、Proプランに加入しました。
ここではV100 GPUのハイメモリを使用しています。
まず必要なモジュールをインストールします。
!pip install llama-index
!pip install transformers accelerate bitsandbytes sentence_transformers sentencepiece
!pip install torch
!pip install python-docx
社内規定については、(さすがに実物は使えないので)下記で公開されている職務権限規定のモデル文書を使わせていただくことにしました。
文書をダウンロードしておきます。
!wget https://www.ohno-jimusho.co.jp/wp-content/uploads/2020/03/%E8%81%B7%E5%8B%99%E6%A8%A9%E9%99%90%E8%A6%8F%E7%A8%8B_04.docx
CUDAが使えることを確認します。
import torch
torch.cuda.is_available()
wordをtxtファイルにいったん変換して、
from docx import Document as word_doc
word_file_url = "/content/職務権限規程_04.docx"
doc = word_doc(word_file_url)
full_text = '\n'.join([para.text for para in doc.paragraphs])
with open('/content/doc_content.txt', 'w') as f:
f.write(full_text)
llama_index.schema.DocumentのListに変換します。
from llama_index import SimpleDirectoryReader
documents = SimpleDirectoryReader(input_files=["/content/doc_content.txt"]).load_data()
これ以降npaka様の記事ほぼそのままです。
変更した点について説明すると、tokenizerとmodelについては
import transformers
from transformers import AutoModelForCausalLM, AutoTokenizer, TextStreamer
tokenizer = AutoTokenizer.from_pretrained("rinna/japanese-gpt-1b", use_fast=False)
model = AutoModelForCausalLM.from_pretrained("rinna/japanese-gpt-1b")
としてrinna/japanese-gpt-1bを使用しています。
また、テキストスプリッタの部分はチャンクサイズが大きすぎてこのモデルでは使えなかったので、100としています。
# チャンクの分割
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=100, # チャンクの最大文字数
chunk_overlap=20, # オーバーラップの最大文字数
)
クエリエンジンを作成したら、きちんと回答できるか確認します。
ちなみに質問の内容については、文書内に明記されています。(推論などは不要です。)
def query(question):
print(f"日本語で回答してください。{question}")
response = query_engine.query(f"日本語で回答してください。{question}")
return response
questions = [
"課長は部下に出張を命じても良い?",
"部長は部下に出張を命じても良い?",
"部長は社長の押印を申請可能か?",
"課長は社長の押印を申請可能か?",
]
for question in questions:
print(query(question))
結果
Q:課長は部下に出張を命じても良い?
A: 課長は出張を命じることができます。ただし、出張命令は部長に申請し、承認を得なければなりません。 ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
完璧な回答です。ただし後ろに大量のハイフンが。
Q:部長は部下に出張を命じても良い?
A:課長は部員の出張を命じることができます。ただし、出張命令は部長がしなければなりません。 ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
間違ってはいませんが、なぜか課長の話をしています。あと後ろに大量のハイフン。
Q:部長は社長の押印を申請可能か?
A:可能です。 ------------------------- 回答: 可能です。 回答: 可能です。 回答: 可能です。 回答: 可能です。 回答: 可能です。 回答: 可能です。 回答: 可能です。 回答: 可能です。 回答: 可能です。 回答: 可能です。 回答: 可能です。 回答: 可能です。 回答: 可能です。 回答: 可能です。 回答: 可能です。 回答: 可能です。 回答: 可能です。 回答: 可能です。 回答: 可能です。 回答: 可能です。 回答: 可能です。 回答: 可能です。 回答: 可能です。 回答: 可能です。 回答: 可能です。 回答: 可能です。 回答: 可能です。 回答: 可能です。 回答: 可能です。 回答: 可能です。 回答: 可能です。 回答: 可能です。 回答: 可能です。 回答: 可能です。 回答: 可能です。 回答: 可能です。 回答: 可能です。 回答: 可能です。 回答: 可能です。 回答: 可能です。 回答: 可能です。
ハイフンの前は正しいですね。後半が怖い。
どうも文字を一定文字数分だけ埋めようとしている気配を感じます。設定が悪い…?
LLM初心者のため、原因が分からず。おいおい調べて追記します。
Q:課長は社長の押印を申請可能か?
A:可能です。 ------------------------- 課長は、部長から、部長の権限に属する事項について、申請を受け付ける権限を与えられています。したがって、課長は、部長から、部長の権限に属する事項について、申請を受け付ける権限を与えられています。したがって、課長は、部長から、部長の権限に属する事項について、申請を受け付ける権限を与えられています。したがって、課長は、部長から、部長の権限に属する事項について、申請を受け付ける権限を与えられています。したがって、課長は、部長から、部長の権限に属する事項について、申請を受け付ける権限を与えられています。したがって、課長は、部長から、部長の権限に属する事項について、申請を受け付ける権限を与えられています。したがって、課長は、部長から、部長の権限に属する事項について、申請を受け付ける権限を与えられています。したがって、課長は、部長から、部長の権限に属する事項について、申請を受け付ける権限を与えられています。したがって、課長は、部長から、部長の権限に属する事項について、申請を受け付ける権限を与えられています。したがって、課長は、部長から、部長の権限に属する事項について、申請を受け付ける権限を与えられています。したがって、課長は、部長から、部長の権限に属する事項について、申請を受け付ける権限を与えられています。したがって、課長は、
課長に許可されているのは「社長印の押印を部長に申請を依頼すること」です。この文だと部長からの申請を課長が受け付けるように見えますね。
また、やはり文字埋めみたいなものが付いてきます。
結論
かなり小規模な文書であり、かつ文書内に答えが明記されている場合であっても、正確に回答することは難しいということが分かりました。(文字埋めは一旦置いておくとしても)
ましてや何百もの社内規定、何千もの記事がある社内Wikiの内容を、1つのLLMに回答させようとするのは現実味が薄い印象です。
正直キーワードでgrepするだけで十分情報が得られるので、それを超えた利便性を獲得するのは(少なくとも個人では)厳しそうですね。
強いて言えば、文書や分野ごとに子AIを作って、Mixture-of-Expertsの形で用意する、とかでしょうか。