見出し画像

【デュエマ】曖昧な質問で裁定検索ができるデモサイト作った

この記事の要約

・こんな感じに曖昧な文章でも関連したデュエマの裁定を検索できるデモサイトを作った。 

・このページで使える。

・デモでしかないので速度・精度・動作は保証しないし新しい裁定にも対応しない。突然消す可能性もあり。お遊び程度のものと思ってもらえれば。


はじめに

こういった文はqiitaとかに投げた方が良いんだろうけど、デュエマプレイヤーにはあまり馴染みがない気がするのでnoteに書く次第。


前に灼熱ドロン・ゴーの裁定に関する記事を書いたが、その時のモチベーションの一つに「もっと公式のルールや裁定を引用して語ってほしい!」というのがあった。

灼熱ドロン・ゴーの裁定が正確に定まる前は、twitterを見る限りはかなりの人が二次情報を参照して議論しており、これはあまり良くないのではと感じていた。

とはいえ事細かにルールや裁定を引用するのが大変なのも事実である。その原因はいくつか存在しているが以下のようなものが挙げられるだろう。

・別に誰も彼もが好き好んでルールや裁定を読んだり記憶したりしてるわけではない
・ルールや裁定の整備が不十分
・裁定が載ってる「よくある質問」ページが検索しにくい

この内上2つは自分1人ではどうしようもないことだが、最後の項目は技術で解消可能なのでどうにかできる。ので試しにやってみた。

問題の整理

「『よくある質問』ページが検索しにくい」とは言うが、「検索しにくさ」にも色々ある。

現状存在する「『よくある質問』ページの検索しにくさ」とは以下のようなものだと思う。

1. AND検索、OR検索ができないなど検索機能が不十分である
2. 「よくある質問」に存在しないシチュエーションやカードの裁定について検索したり、裁定の文章ほどではないもう少し曖昧な文章で検索したりしたいがその手段がない

で、1.については最近定期的にカード検索の改善をしてくれている運営なのでいずれやってくれそうではある。2.については、自分の現状興味ある分野に関連するし実装されなさそうなのでやってみたい。

ということで2.を実装してみる。

実装

※一応「情報解析のための利用」の要件を満たす範囲でやってるのでこれに当てはまる、という理解でやってます。

基本的に以下を参考。


実装といってもデータベースに加工したデータを突っ込んで検索するだけなので大したことではないし、爆速で作ったので雑なところが多いのだがざっくり書いてく。
実装はpython3.10で行う。


要件の実現のために、今回はLangChainのVectorStoreを利用する。

VectorStoreはドキュメントを文字列ではなくベクトル化して保存し、関連するドキュメントの高速検索を実現するための機能を提供している。これにより、入力された文との完全一致や部分一致ではなく、関連度での検索を実現する。
つまるところ「入力された内容に近い文章」を探せるということだ。

データベースにはいろんなサービスが利用できるが、とりあえず目に付いたもので使い方がすぐ分かったQdrantを利用する。ChromaなどはLangChainと相互にドキュメントを置いてたりするので、こっちのほうが良いかもしれない。


まずはデータがなければどうしようもないので「よくある質問」のデータを取得する。ここは取得できればなんでもよいし本旨ではないので省略。


次にVectorStoreを利用する準備。以下のライブラリを入れる。

pip install langchain
pip install qdrant-client
pip install sentence-transformers


次にデータベースを作成する。
この際のembeddingの処理に、OpenAIのAPIを使ってOpenAIEmbeddings()を利用することは可能だが、今後似たようなものを作る可能性があることを考えると料金が発生するAPIを組み込みたくないので、huggingfaceのモデルを使ってembeddingを行う。データの保存方法は色々あるが、記事に倣いつつ扱いやすさを考慮してローカルに指定。

import glob
import json

from langchain.text_splitter import CharacterTextSplitter
from langchain.embeddings import HuggingFaceEmbeddings
from langchain.vectorstores import Qdrant

docs = []

# QAをjsonに保存しているので読み込み
json_pathes = glob.glob("rules/*.json")
for json_path in json_pathes:

    with open(json_path, encoding = "utf-8") as f:
        loaded_json = json.load(f)

    for value in loaded_json.values():
        # QA冒頭のQとAを削除
        q_text = value["q_text"].lstrip("Q")
        a_text = value["a_text"].lstrip("A")
        source = value["answer_url"]
        text_splitter = CharacterTextSplitter(chunk_size=150, chunk_overlap=15)
        doc = text_splitter.create_documents(texts=[q_text], metadatas=[{"source": "Q:" + q_text + "\n" + "A:" + a_text + "\n" + source }])
        print(doc)
        docs.extend(doc)

embeddings = HuggingFaceEmbeddings(model_name="intfloat/multilingual-e5-base")
# db作成
db = Qdrant.from_documents(docs, embeddings, path="local_qdrant", collection_name="qa_data")


今回はLLMを使ったような、「質問と回答」全体を元に文意を考慮した検索するのではなく、入力された質問との関連度の高い質問を検索したいので、回答部分をデータに含めると(カードゲーム特有の固有名詞の多さなどもあり)逆に精度が落ちると考え、質問部分のみをデータベースに入力している。
また検索結果にはQAの全体とURLを含めたいため、metadataのsourceにQA全体とURLを渡している。

注意として、モデルの選定や処理時のパラメータ設定は一番気を払ったほうが良い。元々oshizo/sbert-jsnli-luke-japanese-base-liteを使ってテストしていたが全く良い結果が出ず困っていたところ、intfloat/multilingual-e5-largeに変更したら一気に精度が良くなった。まあembeddingは「どのように文章を理解するか」という処理そのものなので、ここを重要視するのは当たり前ではある…。

パラメータは主にchunk_sizeの設定になるが、利用するモデルやサービスによって必要なサイズが変わってくる。また、サイズによって検索の精度も変わってくるのでどれが適切かを探る必要がある。一応自分はいくらか試してchunk_size=150に設定したが、別の最適解が存在するかもしれない。


データベースが作成できたらあとは検索するだけである。検索のコードは以下(データベースは一度作成したらそれを使い回せば良いので上記のコードとは別にする)。

from langchain.embeddings import HuggingFaceEmbeddings
from langchain.vectorstores import Qdrant
from qdrant_client import QdrantClient

embeddings = HuggingFaceEmbeddings(model_name="intfloat/multilingual-e5-base")
client = QdrantClient(
    path="./local_qdrant", prefer_grpc=True
)
db = Qdrant(client=client, embeddings=embeddings, collection_name="qa_data")

query = "芸魔龍王アメイジンの出た時の効果は、後から出たクリーチャーも影響しますか"
# kは出力する結果の数
docs = db.similarity_search_with_score(query=query, k=10)
for i in docs:
    doc, score = i
    print({"score": score, "content": doc.page_content, "metadata": doc.metadata} )

このコードにある「芸魔龍王アメイジンの出た時の効果は、後から出たクリーチャーも影響しますか」という文章は「よくある質問」には存在しないのだが、この検索結果(doc.metadataの部分のみ)は以下。

Q:相手の《芸魔龍王 アメイジン》が出た次の自分のターンに「スピードアタッカー」を持つクリーチャーを出した場合、そのクリーチャーは相手を攻撃できますか?
A:はい、攻撃できます。
《芸魔龍王 アメイジン》の能力で攻撃もブロックもできなくなるのは、その効果を解決した時点でバトルゾーンにいたクリーチャーだけです。
https://dm.takaratomy.co.jp/rule/qa/43771/

Q:≪オラオラ・スラッシュ≫を唱えた後でバトルゾーンに出たクリーチャーは、≪オラオラ・スラッシュ≫の効果を受けますか?
A:いいえ、≪オラオラ・スラッシュ≫の効果は唱えた時にバトルゾーンにいた相手のクリーチャーにだけ影響を及ぼします。唱えた後にバトルゾーンに出たクリーチャーには影響を及ぼしません。
https://dm.takaratomy.co.jp/rule/qa/33071/

Q:相手の《芸魔龍王 アメイジン》の「出た時」の能力を解決した後、そのターン中に自分は《デーモン・ハンド》を唱えて《芸魔龍王 アメイジン》を破壊しました。
相手の手札は7枚ありますが、自分は《芸魔龍王 アメイジン》の効果を受けているコスト7のクリーチャーで攻撃やブロックできますか?
A:いいえ、攻撃もブロックもできません。
《芸魔龍王 アメイジン》が破壊されても、その効果は残っています。《芸魔龍王 アメイジン》の効果を解決した時点でバトルゾーンにいた、相手の手札の枚数以下のコストを持つ自分のクリーチャーは攻撃もブロックもできません。
https://dm.takaratomy.co.jp/rule/qa/43773/

Q:相手の《芸魔龍王 アメイジン》が出た後、そのターンに自分は「S・トリガー」でクリーチャーを出しました。
そのクリーチャーが「ブロッカー」を持つ場合、そのターン中にブロックできますか?
A:はい、ブロックできます。
《芸魔龍王 アメイジン》の能力で攻撃もブロックもできなくなるのは、その効果を解決した時点でバトルゾーンにいたクリーチャーだけです。
https://dm.takaratomy.co.jp/rule/qa/43770/

Q:≪チョウハツ・チュリス≫をバトルゾーンに出した後でバトルゾーンに出たクリーチャーは、≪チョウハツ・チュリス≫の「このクリーチャーがバトルゾーンに出た時」の効果を受けますか?
A:いいえ、≪チョウハツ・チュリス≫の効果は出した時にバトルゾーンにいた相手のクリーチャーにだけ影響を及ぼします。出した後にバトルゾーンに出たクリーチャーには影響を及ぼしません。
https://dm.takaratomy.co.jp/rule/qa/33075/

いい感じなので良し!

というか「『アメイジン』という単語は入ってないが同じルールに基づく裁定」もちゃんと拾ってるのでかなり良い。デモとしては十分だろう。

ということで、huggingfaceで使えるようにちょっとだけ修正して終わり。


雑感

・元々はRetrievalQAを使ってLLMに回答してもらうことで文意を考慮して回答してもらおうと思っていたが、回答が遅いし動作は重いし思ったより適切に答えが返ってこないし…という感じで面倒だったので、関連度で検索できればいいやと とりあえずこの形で実装した。
簡単な質問ならこれだけで思った以上にちゃんと関連した裁定を探せるし、そもそも固有名詞がいくつも存在する細切れのQA集というデータセットで調整してどこまでうまい返しができるのか?というのもあった。

・カードや総合ルールのデータ、よくある質問のQA全体のテキスト込みでいい感じにチューニングしてLLMに答えさせればまた別の結果は得られるだろうが、めんどくさいので機械学習好きで強いGPUを持ってるDMPに任せたい。

・カードゲームの裁定のテキストは固有名詞が非常に多いので、embeddingの際の処理が特に大事というのを身にしみて感じた。あらゆる学習に言える。

・ひとまずテキスト関連はこのくらいにして、次は遊戯王のニューロンに搭載されているようなカード認識機能を作りたい。かなり前に簡素なものは作ったが(下みたいなの)、今ならより良い精度で作れそう。

・サイトの使い方として、基本的に「よくある質問」の文章は敬体で書かれているので、ですますで質問を入力したほうが精度は高い。また複雑な質問だと関連回答が出にくいので、表示数を増やしたり、固有名詞はなるべく正式名称を入れるようにしたりして検索してもらえれば。ただし出ないときは出ない。デモなのであしからず。



データベースでも機械学習でもなんでもやるんでタカラトミー雇ってください!!

この記事が気に入ったらサポートをしてみませんか?