💭

ローカルLLMで遊戯王QAを作る [LangChain x CSV x Huggingface]

2023/12/02に公開

コード
https://github.com/Exe-dev/YGOLLM/tree/main

この記事でわかること

LangChainでHuggingface(HF)のモデルを動かす方法。

目標

LLMに遊戯王について答えられるようになってほしいので、遊戯王のカード情報が収まったCSVを渡してそれを反映した応答を生成できるようにする。

あと遊戯王やるNLPerを増やす。

事前準備

生成のためにtransformersとlangchain系統を使うのでそれらをインストール。

pip install transformers langchain langchain_experimental

日本語LLMでの問題点

当初はlangchainのcreate_csv_agentを使ってcsvの中身をLLMに渡す予定だったが、これと同じような問題が起こりうまくいかなかったため断念。
おそらくローカルLLMだと、精度が低く、出力文が指定通りのフォーマットにならないためparserが解析できずエラーが出てる。

解決策

create_csv_agentでは英語の"tiiuae/falcon-7b-instruct"と英語のCSVデータを使うことに。

日本語での応答生成は、日本語のCSVデータとプロンプト頼みで実装。

csv_agentを用いた実行

https://github.com/Exe-dev/YGOLLM/blob/main/LLM.ipynb

LLM.ipynb
import warnings
warnings.filterwarnings('ignore')
from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline, set_seed
import pandas as pd
import torch
from langchain.llms import HuggingFacePipeline
from langchain_experimental.agents.agent_toolkits import create_csv_agent, create_pandas_dataframe_agent
from langchain.agents.agent_types import AgentType


MNAME = "tiiuae/falcon-7b-instruct"
INPUT_CSV = "./data/cardinfo_en.csv"
df = pd.read_csv(INPUT_CSV)
df.tail(3)
"""
ruby	card_name	effect
97	NaN	Accel Synchro Stardust Dragon	1 Tuner + 1+ non-Tuner monstersIf this card is...
98	NaN	Accel Synchron	1 Tuner + 1+ non-Tuner monstersOnce per turn: ...
99	NaN	Accellight	If you control no monsters: Special Summon 1 L.
"""

question = f'what effect is the {df["card_name"][98]} ?'
question # 'what effect is the Accel Synchron ?'

# モデルの読み込み
tokenizer = AutoTokenizer.from_pretrained(MNAME, padding=True, truncation=True, max_length=50, add_special_tokens = True)
pipe = pipeline(
    "text-generation", model=MNAME, tokenizer=tokenizer, device=0, max_new_tokens=500, torch_dtype=torch.float16, temperature=0.3, repetition_penalty=1
)
llm = HuggingFacePipeline(pipeline=pipe)
pipe(question)

# csv agentの作成
agent = create_csv_agent(
    llm,
    INPUT_CSV,
    verbose=True,
    agent_type=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
    handle_parsing_errors=True,
)
agent.run(question) # agentに対して質問

効果を聞いたカードはこれ

正解文en&jaは以下の通り。

正解文
1 Tuner + 1+ non-Tuner monsters
Once per turn: You can send 1 "Synchron" monster from your Deck to the GY, then activate 1 of these effects;
●Increase this card's Level by the Level of the sent monster.
●Reduce this card's Level by the Level of the sent monster.
During your opponent's Main Phase, you can (Quick Effect): Immediately after this effect resolves, Synchro Summon using this card you control. You can only Synchro Summon "Accel Synchron" once per turn.
**日本語**
チューナー+チューナー以外のモンスター1体以上
自分は「アクセル・シンクロン」を1ターンに1度しかS召喚できない。①:1ターンに1度、デッキから「シンクロン」モンスター1体を墓地へ送り、以下の効果から1つを選択して発動できる。●墓地へ送ったそのモンスターのレベル分だけ、このカードのレベルを上げる。●墓地へ送ったそのモンスターのレベル分だけ、このカードのレベルを下げる。②:相手メインフェイズに発動できる。このカードを含む自分フィールドのモンスターをS素材としてS召喚する。

まずcsv agentを使わないモデルのみの出力。

モデルのみの予測
what effect is the Accel Synchron ?\nThe Accel Synchron is a device that can be used to synchronize the speed of a motor or other mechanical device. It works by using a high-speed motor to drive a gear that is connected to the device, which in turn drives the mechanical device. The effect of the Accel Synchron is to synchronize the speed of the mechanical device with the speed of the motor, which can be useful in applications such as robotics or automation.
**日訳**
アクセルシンクロンはどのような効果がありますか? ➀アクセルシンクロンは、モーターやその他の機械装置の速度を同期させるために使用できる装置です。高速モーターを使用して、装置に接続されたギアを駆動し、そのギアが機械装置を駆動します。アクセルシンクロの効果は、機械装置の速度とモーターの速度を同期させることであり、ロボット工学やオートメーションなどの用途に役立ちます。

明らかにアクセル部分に引っ張られてる。

ロボット工学やオートメーションなどの用途に役立ちます。

残念ながら遊戯王にしか役立てることはできません...

次にcsv agentを用いた際の出力。

agentの予測
'the final answer to the original input question

The Accel Synchron is a card that can be used to increase the speed of your monsters. It is a spell card that can be used during the Main Phase of a turn. The effect of the card is to increase the speed of all monsters on the field by 1. The card can be used to increase the speed of monsters that are not in the field, but it cannot be used to increase the speed of monsters that are already in the field.'
**日訳**
アクセル・シンクロンはモンスターのスピードを上げるカードです。ターンのメインフェイズに使用できるスペルカードです。このカードの効果はフィールド上の全てのモンスターのスピードを1上げることです。このカードはフィールド上に存在しないモンスターのスピードを上げるために使用することはできますが、既にフィールド上に存在するモンスターのスピードを上げるために使用することはできません。

なんかそれっぽい説明が出てきてる。
「スピード」→「レベル」と読み替えればある程度正解な気もする。
これはLLMに遊戯王を教えたといっても過言ではないのでは。

小ネタ

実は遊戯王にはスペルスピードという概念があるので、スピードという単語は実はHallucinationではない。

気合のPrompting

実はcsv agentは裏でcsvの中身をPrompt templateに当てはめて生成しているにすぎないっぽい(?).なので、Promptを真似て生成することを試みる。
Prompt templateは以下のように取得できる。

prompt_template
agent = create_csv_agent(
    llm,
    INPUT_CSV,
    verbose=True,
    agent_type=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
    handle_parsing_errors=True,
)
print(agent.agent.llm_chain.prompt.template)
"""
You are working with a pandas dataframe in Python. The name of the dataframe is `df`.
You should use the tools below to answer the question posed of you:

python_repl_ast: A Python shell. Use this to execute python commands. Input should be a valid python command. When using this tool, sometimes output is abbreviated - make sure it does not look abbreviated before using it in your answer.

Use the following format:

Question: the input question you must answer
Thought: you should always think about what to do
Action: the action to take, should be one of [python_repl_ast]
Action Input: the input to the action
Observation: the result of the action
... (this Thought/Action/Action Input/Observation can repeat N times)
Thought: I now know the final answer
Final Answer: the final answer to the original input question


This is the result of `print(df.head())`:
{df_head}

Begin!
Question: {input}
{agent_scratchpad}
"""

上記のプロンプトを日本語に直して日本語LLMで生成してみる。
コードの大部分は英語部分と同じで、日本語の方はagentではなくpipelineで直接生成している。

日本語版template
MNAME = "rinna/japanese-gpt-neox-3.6b-instruction-sft" 
INPUT_CSV = "./data/cardinfo_ja.csv"
df = pd.read_csv(INPUT_CSV)
df.tail(3)
"""
	ruby	card_name	effect
97	あくまのくちづけ	悪魔のくちづけ	①:装備モンスターの攻撃力は700アップする。②:このカードがフィールドから墓地へ送られた時...
98	あくまのサイコロ	悪魔のサイコロ	①:サイコロを1回振る。相手フィールドのモンスターの攻撃力・守備力は、ターン終了時まで出た目...
99	あくまのちえ	悪魔の知恵	このカードの表示形式が攻撃表示から表側守備表示に変わった時、自分のデッキをシャッフルする。
"""
question = f'{df["card_name"][99]}の効果は?' # 悪魔の知恵の効果は?

tokenizer = AutoTokenizer.from_pretrained(MNAME, padding=True, truncation=True, max_length=50, add_special_tokens = True)
pipe = pipeline(
    "text-generation", model=MNAME, tokenizer=tokenizer, device=0, max_new_tokens=1024, torch_dtype=torch.float16, temperature=0.3, repetition_penalty=1
)
llm = HuggingFacePipeline(pipeline=pipe)
pipe(question)
template = f"""
template = f"""
あなたはPythonでpandasのデータフレームを操作している。データフレームの名前は `df` です。
以下のツールを使って、与えられた質問に答えてください:

以下の書式を使用してください:

質問:あなたが答えなければならない入力質問
Thought(思考):何をすべきかを常に考える。
観察:行動の結果
... (この思考/行動/行動入力/観察はN回繰り返すことができる)
思考: 最終的な答えがわかった
最終的な答え:元の入力された質問に対する最終的な答え


これは `print(df.head())` の結果である:
{df.head(100)}

開始!
質問 {question}
"""

print(template)
"""
generated = pipe(template)
print(generated[0]["generated_text"])
"""
あなたはPythonでpandasのデータフレームを操作している。データフレームの名前は `df` です。
以下のツールを使って、与えられた質問に答えてください:

以下の書式を使用してください:

質問:あなたが答えなければならない入力質問
Thought(思考):何をすべきかを常に考える。
観察:行動の結果
... (この思考/行動/行動入力/観察はN回繰り返すことができる)
思考: 最終的な答えがわかった
最終的な答え:元の入力された質問に対する最終的な答え


これは `print(df.head())` の結果である:
                                   ruby                            card_name  \
0     Aggiba, the Malevolent Sh'nn S'yo    Aggiba, the Malevolent Sh'nn S'yo   
1          Armament of the Lethal Lords         Armament of the Lethal Lords   
2        Chimaera, the Master of Beasts       Chimaera, the Master of Beasts   
3                  Emperor of Lightning                 Emperor of Lightning   
4   Gatebridgeo the Waterfront Warbeast  Gatebridgeo the Waterfront Warbeast   
..                                  ...                                  ...   
95                            あくまじょうリリス                               悪魔嬢リリス   
96                            あくまじょうロリス                               悪魔嬢ロリス   
97                             あくまのくちづけ                              悪魔のくちづけ   
...

開始!
質問 悪魔の知恵の効果は?
答え:3
"""

正解はこれ

答え:3

Promptに答えが入ってるのに、遊戯王っぽい文ですらなくダメそう。

3.6Bだとモデルサイズが小さそうなので、rinna/youri-7b-instructionに変えて再度実行。

答え 自分のデッキをシャッフルする

こちらは正解。
日本語LLMもagentは無理そうだけど、7BクラスならPromptベースで同じぐらいの精度を出せそう?

感想

数行のコードでCSVの情報を使ったChat Agentを作れるlangchainすごい。
日本語agentはうまく動かなかったが、英語モデルでは間違っていたものの遊戯王の特徴をとらえたテキストを生成できていて流石だった。
日本語もPromptベースなら同じぐらいやれそうな気配は感じた。
いずれはおしゃべりしながら決闘のできるChat botができたらうれしい。

再現用のデータはGithubに上げてあるので、だれか遊戯王対話システム作って。

Discussion