戦国武将クイズに答えるRAGを構築して評価を自動化したら爆速でした
2024年01月29日 月曜日
CONTENTS
こんにちは。暖冬と言われていたのに毎日寒いのは年齢のせいでしょうか。しゅっちんです。久しぶりのエンジニアブログへの投稿となります。今回は、話題の生成AIに関連してRAGの検証を行いたいと思います。
RAGについて
昨年は生成AI元年と言ってよい年でしたが、その生成AIに関連する注目技術の一つがRAGです。RAGとは Retrieval Augmented Generationの略で、日本語で検索拡張生成となります。訳すと余計に意味が分からなくなりますが、要はGPTなどの会話応答AIに関連情報を渡して、その情報を元に質問に回答させる仕組みです。
GPTなどの言語モデルは学習した情報が古かったり、専門知識が不足していたりしますが、RAGによって新しい知識や情報を得ることができ、専門的な質問にも答えることができるようになるのです。
RAGはわりと簡単に導入できるため、会話応答AIの応答の改善によく利用されています。ところが最近、RAGの精度や正答率が思ったように上がらず導入を見送った、といった事例が公開されるようになりました。私もRAGに取り組んだのですが、以下のような課題があると感じました。
- どんなに頑張っても正答率100%にはならないこと
- わからないことをわからないと答えず、嘘を回答する場合があること
会話応答AIのハルシネーションは未解決と言われているので仕方のないことかもしれません。あとは業務の性質上、誤回答をどこまで許容し、どこまで正答率を上げる必要があるかの判断になると思います。
検証の概要
今回行う検証は、戦国武将に関するクイズを用意しRAGでどの程度回答できるかを評価するというものです。戦国武将というと織田信長や武田信玄などが有名ですね。RAGはAzure上にで構築し、会話応答AIはAzure OpenAI を利用します。
システム構成
Azure OpenAI
システム構成ですが、Azure OpenAI(AOAI)を使ったRAGの一般的な構成です。AOAI公式のRAGとも言える「Add Your Data」 もこのような構成になってますよね。
ちなみに表などのより複雑なレイアウトの文書を読み込む場合にはAzure Document Intelligence(旧Azure Form Recognizer) を使うこともあります。今回は取り込むデータが文章のみなので不要です。
戦国武将データセットの作成
検索システムである Azure AI Search(旧 Cognitive Search)のインデックスを作成する必要がありますが、まずはその元になる戦国武将に関するデータセットが必要です。そこで1300名以上の武将について pythonの wikipediaapiで Wikipediaから情報を取得し、”武将名”.txt という形で武将ごとのテキストファイルを作成しました。以下はその一部です。
戦国武将インデックスの作成
そしてそれぞれのファイルの中身をチャンク分割し、ベクトル情報と共にインデックス化しました。チャンクサイズは1280トークン、オーバーラップは128トークンです。マイクロソフトのRAGに関するレポートでは512トークン、25%のオーバーラップが最も結果がよかったとありますが、今回日本語ということもありチャンクを大きめにしています。このあたり正解は無いので、評価しながら最適サイズを探っていくしかないと思います。
RAGアプリケーションの作成
次にStreamlitでRAGアプリケーションを作成しました。
Streamlit
StreamlitはpythonだけでWebアプリケーション作成が可能なフレームワークです。Streamlitにしたのは簡単なのと、私が JavaScriptができないからですね。JavaScript系であればMicrosoftやAzureのGithubにクールなデザインのサンプルがいろいろ転がっているので試してみてください。
クイズを入力すると、その内容を検索システムに投げて検索結果を受け取ります。そしてその検索結果の上位3つをGPTに参考情報として渡して、それをベースにクイズに答えてもらうという仕組みです。またサイドバーでGPTモデルやインデックス名、top_pなどの検索パラメータなどをUIから設定できるようにしています。
戦国武将に関するクイズの作成
次に戦国武将に関するクイズを準備します。RAGを定量的に評価するにはある程度の数が必要で、100ぐらいは欲しいところです。戦国武将のクイズを考えるのも楽しいですが、大量のクイズを考えるのは時間がかかります。こういう時こそ生成AIに頑張ってもらいましょう。
Semantic Kernel
GPTに作成を依頼するプログラムでは Microsoftが提供しているLLMオーケストレーターである Semantic Kernelを使いました。Semantic Kernelを簡単に言うと LangChainみたいなものです。元からいろいろなスキルが用意されており、読み込むだけで使うことができますし、自作することもできます。Semantic Kernelの詳細については以下を参照してください。
https://learn.microsoft.com/ja-jp/semantic-kernel/
今回は以下のようなプロンプトのスキルを作成しました。
prompt = """以下の武将情報が書かれたテキストを元に、武将名を当てるクイズを考えてください。 クイズはこのテキストの情報がなければ答えられない難易度の高いものにすること。 クイズは2、3行程度の長さにしてください。 必ず日本語を使ってください。 アウトプットのフォーマットは以下の形式としてください。 "question","answer" "{{$INPUT}}" """
武将名を当てるクイズにしたのは、クイズを普通にGPTに作らせると、武将名が先頭に来るクイズを作ることがほとんどだったからです。例えば以下のような形式のクイズです。
“安田顕元が川中島の戦いで功績を挙げた後、任された信濃国の城はどこか?”,”飯山城”
これでは”安田顕元”というキーワードが強力で、検索失敗がほぼ発生しません。逆に武将名を当てるクイズであれば、より高い検索精度が必要になってきます。
さて、時間はかかりましたが、約1300名の武将に関して、クイズと正解のペアを約1800件作成しました。以下はその一部です。
"天正壬午の乱において上杉景勝麾下の「梶田」と共に小笠原洞雪斎を擁立し、深志城を奪還した武将は誰でしょう?","屋代秀正" "この武将は、徳川家康の片腕として活躍し、小牧・長久手の戦い後に豊臣秀吉に臣従し、信濃松本藩の初代藩主とされる人物です。家康の嫡男・信康と正室・築山殿を今川氏から取り戻した功績もありますが、出奔の理由は諸説あり定かではありません。この武将の名前は何でしょう?","石川数正" "この武将は日向伊東氏の家臣で、永禄10年に敵方の武将を討ち取り、伊東姓を賜ったが、後に長倉に復姓した。耳川の戦いで次男と共に自害したのは誰でしょう?","長倉祐政" "この武将は肥後国の相良氏に仕え、永禄7年に大口城で島津軍と戦い、永禄11年には島津義弘を窮地に追い込んだ功績がありますが、天正2年の文書を最後に史書から名前が見られなくなりました。この武将の名前は何でしょう?","赤池長任" "天正10年に生まれ、伊達政宗の従兄にあたり、最上氏第12代当主となったこの武将は、大坂の陣で豊臣氏と懇意にあった弟を誅殺し、徳川方に与したことで知られています。享年36歳で急死し、その死因には謎が残されていますが、彼の名前は何でしょう?","最上家親" "駿河下向後、龍王丸が成人するまで範満を家督代行とすることで決着させた調停を行ったこの武将は誰でしょう?","伊勢宗瑞(北条早雲)" "この武将は、庶長子でありながら家督を継ぎ、内紛を収拾して相良氏を戦国大名へと成長させた。また、鷹峯城を築き、分国法を定めたことでも知られています。この武将の名前は何でしょう?","相良義滋" "三好政長の子であり、兄弟の中で宗渭とされる人物とは異なる、同じく三好氏一族の戦国武将は誰でしょう?","三好為三" "関ヶ原の戦いにおいて、美濃の諸勢の大半を率いて西軍に加勢し、最終的に岐阜城で降伏したこの武将は誰でしょう?","織田秀信" "この武将は大内義興の下で博多代官を務め、大内氏の家臣であったが、天文20年には陶隆房の謀反に味方し、大内義隆を自害に追い込んだ。彼の名前は何でしょう?","飯田興秀" "「謀聖」「謀将」と称され、毛利元就や宇喜多直家と並ぶ中国の三大謀将の一人とされるこの武将の名前を答えなさい。","尼子 経久" "戦国時代から江戸時代初期にかけて活躍し、因幡山名氏に仕え、防己尾城主であったこの武将は、後に毛利氏のもとを離れ諸国を放浪した後、帰農したとされるが、その名前は何か。","吉岡定勝" "この武将は六角氏に仕え、六角六宿老の一人であったが、兄が討死した後に家督を継ぎ、六角氏式目に署名しています。織田信長の侵攻後、独立勢力として六角氏の再起を支援しましたが、最終的には織田氏に降伏し、後に蒲生氏郷に仕えたこの武将の名前は何でしょう?","三雲成持" "この武将は、毛利氏に仕えた外交僧であり、豊臣秀吉からも知行を貰って大名に取り立てられたとされるが、異説もある。また、秀吉の九州征伐後には6万石に加増されたという記録があるが、その真偽には議論がある。この武将の名前は何でしょう?","安国寺恵瓊" "天文22年にわずか17歳で島津氏の家老職に抜擢され、谷山の地頭にも任命されたこの武将は誰か。","川上久朗" "この武将は、出羽国の戦国大名であり、檜山系と湊系の安東氏を統一し、安東氏の戦国大名化を成し遂げたとされる人物です。天正5年には従五位下に叙され、天正8年には従五位上侍従となりました。晩年には名字を何に改めたでしょうか?","秋田" "この武将は、織田信長を暗殺しようとしたが、羽柴秀吉に妨害されたとされる戦国時代の人物です。彼の名は何でしょう?","遠藤直経"
なかなかマニアックなものもありますが、いい感じのクイズになっていますね。全ての質問が同じプロンプトから作成されるためパターンが似ていますが、スキルを複数作って質問のバリエーションを持たせるように工夫するとよいかもしれません。
WebUIで質問してみる
さてクイズが準備できたので、実際にGPTに投げて応答を見ていきましょう。
おお、きちんと正解を答えてくれますね。ただクイズによっては回答できないこともあるようです。
今回のクイズは全て元データからAIが生成しているので、回答に必要な情報は必ずインデックスに存在するはずです。にもかかわらず回答できないということは、クイズがおかしいか、検索に失敗しているということになります。先ほどのケースではクイズ自体は問題なさそうなので、検索に失敗している可能性が高いです。実際にどのドキュメントがGPTに渡されたのか見てみましょう。
立花誾千代の情報をGPTに渡すべきなのに、加藤清正の情報しか渡していませんね。これでは間違った回答をしてしまうのは当然です。実はRAGではこういったケースが非常に多く見られます。
RAGは検索が全て
RAGの一番の課題は検索です。そもそも正しい情報をAIに渡すことができなければ、回答できるはずがありません。これは人間も同じだとおもいます。
また今回のように取り込み対象の文書がテキストだけであればよいですが、図や表、フローなどが混じった文書では、さらに大きく検索精度が落ちます。よって、RAGの応答精度改善の取り組みイコール、検索精度向上の取り組みと言っても過言ではありません。検索に始まり検索に終わる、というのがRAGです。
検索精度を向上させるには?
ではどうすれば検索精度を向上させることができるでしょうか。一般的には以下のような取り組みを行います。
- ベクトル検索
- スコア判定
- リランキング
- チャンクサイズ調整
- 生成AIに渡す検索数の調整
- 検索する際のクエリー改善
他にもいろいろありますが、まずはこういったことを試すと思います。ただし、どの取り組みが有効かはケースバイケースなので、実際にいろいろ何度も試して比較評価する必要があるのです。これがRAGの大変なところです。
評価手法
さらに、RAGの精度向上の取り組みを行う上で重要になるのが評価手法です。
先ほど述べたように、何が効果があるかは検証してみないとわからないため、評価と改善のサイクルを何回も、何十回も繰り返す必要があります。ですが、AIへ質問したり回答を評価するタスクを人力で行うことには限界がありますし、評価の基準も人によってバラバラなため、正しい評価ができない可能性も高いです。
そのため最近よく使われているのが、RAGの自動評価ツールです。いくつかありますが、例えば以下のようなものが有名です。
-
- RAGAS
- Azure AI Prompt Flow
今回はRAGASを使って評価を行うことにします。
RAGAS
RAGASはメジャーなRAG評価ツールで、内部的には langchainを使っています。
https://docs.ragas.io/en/stable/
正解とAIの回答を比較するだけでなく、コンテキスト、つまり検索結果との関連性なども評価することができます。また検索、質問、回答、評価を全て自身で行うこともできます。ですが今回はあらかじめクイズと正解、生成AIの回答を準備して、それをRAGASに評価させることとします。
あと、さほど気にすることもないかと思いますが、利用状況を取得されたくなければ RAGAS_DO_NOT_TRACK フラグを true にしておく必要があるようです。
https://github.com/explodinggradients/ragas/issues/49
RAGASはさまざまな指標で評価することできますが、今回は正解と回答の関連性が見たいので以下の指標を使います。
- Answer semantic similarity
- Answer correctness
回答の文脈的な類似性の指標が Answer semantic similarity で、回答の正確性が Answer correctness となります。今回は回答自体が短いので Answer correctness を中心に見ればよいでしょう。
回答の自動生成
RAGASで評価するためにはAIの回答も必要です。クイズの数が10とは20ぐらいであれば先ほどのようなWebUIを使って回答をコピーすればよいと思います。ですが、質問数が多い場合は時間と手間がかかりすぎるため、こちらも自動化します。
今回はpythonプログラムで回答を自動生成するようにしました。どのドキュメントをAIに渡したかの情報も重要なので、RAGASの評価には使いませんが、データとして残しておきます。
コンテンツフィルタに注意
Azure OpenAIのモデルは暴力や性描写などにコンテンツフィルタがかかるようになっていますが、それによって回答を生成する処理が途中で止まるということがよくありました。ですので、プログラムを回す際は、デフォルト設定の「中、高をブロック」ではなく「高をブロック」に変更しておいた方がよいとおもいます。
いよいよ評価してみる
さて、RAGASでの評価に必要な、戦国武将に関する約1800問のクイズ、正解、AIの回答、検索結果が揃いました。大変でしたが、全て自動化できたのであとは楽です。
いよいよ実際に評価してみたいと思います。とりあえず10問試してみましょう。渡したデータは以下になります。
"question","ground_truths","answer" "天文18年に晴元の重臣・三好長慶が裏切り、敗北したことで細川氏の所領を奪われ、最終的に没落することになった和泉上守護家の当主は誰でしょう?","細川元常","細川元常" "この武将はフィリピン侵略を計画し、そのためにマニラにスパイを送ったが、計画実行前に急死したとされるのは誰でしょう?","松倉重政","松倉重政" "この武将は飛騨国帰雲城主であり、天正地震で城とともに滅亡した一族の最後の当主です。上杉謙信に臣従していた姉小路頼綱の侵攻を撃退し、後に織田政権とも関わりを持ちましたが、羽柴秀吉の侵攻により降伏。その名前は何でしょう?","内ヶ島氏理","内ヶ島氏理" "戦国時代から江戸時代初期にかけての武将で、今川義元の嫡子として生まれ、桶狭間の戦いで父が討たれた後に今川家の当主となったものの、武田信玄と徳川家康による駿河侵攻を受けて敗れ、最終的には徳川家康と和議を結んで臣従したこの人物の名前は何でしょう?","今川氏真","今川氏真" "この武将は、陸奥国の戦国大名であり、室町幕府の奥州探題を務めた家柄の出身です。兄が早世したため家督を継ぎ、伊達氏に従属するものの、後に伊達氏内の争いに介入して従属状態から脱却を試みました。この武将の名前は何でしょう?","大崎 義直","大崎義直" "文禄3年(1594年)に豊臣秀次の奏請により従五位下・遠江守に叙任され、関ヶ原の戦いの前哨戦である大聖寺城の戦いで前田軍の先鋒を務めたこの武将の名前を答えなさい。","津田重久","津田重久" "この武将は武田信玄の兄であり、画家としても知られ、甲府の大泉寺に所蔵される「武田信虎像」を残しています。また、信玄の影武者を務めたことで知られ、信玄の死を隠すためにその役割を果たしたとされるのは誰でしょう?","武田信廉","武田信廉" "この武将は、新田金山城を奪い、由良氏を戦国大名として独立させた人物ですが、彼が鉄炮好きであることを知った将軍から鉄炮一丁を贈られたのは何年でしょう?","天文22年(1553年)","天文22年(1553年)" "宇喜多氏三老の一人で、宇喜多直家に仕え、岡山城の築城を任されたこの武将は、通称を豊前守といい、文禄の役で朝鮮に出征中に病没した。名前は何という?","岡家利","岡家利" "この武将は、戦国時代に女性ながら城督を継ぎ、加藤清正の軍に迂回させるほどの威光を持っていたとされる女性武将です。 彼女の名前は何でしょう?","立花 誾千代","山崎氏"
ほとんど正解なのですが、最後の質問だけ相変わらず間違っています。
この内容でRAGASの評価がどうなるか試してみましょう。
$ python evaluate.py ../rolling_answer/answer_01.txt evaluating with [answer_correctness] 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████| 1/1 [00:09<00:00, 9.74s/it] evaluating with [answer_similarity] 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████| 1/1 [00:01<00:00, 1.27s/it] {'answer_correctness': 0.8676, 'answer_similarity': 0.9762}
回答の正確性が 0.8676 と出ました。10問のうち、1問不正解だとこれくらいの数値になるということですね。
では試しに最後の回答を正解に変えて、試してみます。AIの回答を “山崎氏” ではなく “立花誾千代” に修正しました。
evaluating with [answer_correctness] 100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 1/1 [00:11<00:00, 11.75s/it] evaluating with [answer_similarity] 100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 1/1 [00:02<00:00, 2.23s/it] {'answer_correctness': 0.9353, 'answer_similarity': 0.9929}
0.9353まで上がりました。0.99ぐらいになるかと思いましたが、違いました。スペースが入っている、いないでも変わってくるのかもしれません。
回答を冗長にしてみる
次に検証として、以下のようなAIの回答が冗長なバージョンを評価してみたいと思います。回答自体は全て正解です。
"question","ground_truths","answer" "天文18年に晴元の重臣・三好長慶が裏切り、敗北したことで細川氏の所領を奪われ、最終的に没落することになった和泉上守護家の当主は誰でしょう?","細川元常","和泉上守護家の当主で、天文18年に晴元の重臣・三好長慶が裏切り、敗北したことで細川氏の所領を奪われ、最終的に没落することになったのは細川元常です。" "この武将はフィリピン侵略を計画し、そのためにマニラにスパイを送ったが、計画実行前に急死したとされるのは誰でしょう?","松倉重政","この武将は松倉重政です。彼はフィリピン侵略を計画し、マニラにスパイを送ったが、計画実行前の寛永7年(1630年)に小浜温泉で急死したとされています。" "この武将は飛騨国帰雲城主であり、天正地震で城とともに滅亡した一族の最後の当主です。上杉謙信に臣従していた姉小路頼綱の侵攻を撃退し、後に織田政権とも関わりを持ちましたが、羽柴秀吉の侵攻により降伏。その名前は何でしょう?","内ヶ島氏理","この武将の名前は内ヶ島氏理(うちがしま うじまさ/うじとし)です。" "戦国時代から江戸時代初期にかけての武将で、今川義元の嫡子として生まれ、桶狭間の戦いで父が討たれた後に今川家の当主となったものの、武田信玄と徳川家康による駿河侵攻を受けて敗れ、最終的には徳川家康と和議を結んで臣従したこの人物の名前は何でしょう?","今川氏真","この人物の名前は今川氏真(いまがわ うじざね)です。" "この武将は、陸奥国の戦国大名であり、室町幕府の奥州探題を務めた家柄の出身です。兄が早世したため家督を継ぎ、伊達氏に従属するものの、後に伊達氏内の争いに介入して従属状態から脱却を試みました。この武将の名前は何でしょう?","大崎 義直","この武将の名前は大崎義直(おおさき よしなお)です。" "文禄3年(1594年)に豊臣秀次の奏請により従五位下・遠江守に叙任され、関ヶ原の戦いの前哨戦である大聖寺城の戦いで前田軍の先鋒を務めたこの武将の名前を答えなさい。","津田重久","津田重久です。" "この武将は武田信玄の兄であり、画家としても知られ、甲府の大泉寺に所蔵される「武田信虎像」を残しています。また、信玄の影武者を務めたことで知られ、信玄の死を隠すためにその役割を果たしたとされるのは誰でしょう?","武田信廉","この武将は武田信廉(たけだ のぶかど)です。" "この武将は、新田金山城を奪い、由良氏を戦国大名として独立させた人物ですが、彼が鉄炮好きであることを知った将軍から鉄炮一丁を贈られたのは何年でしょう?","天文22年(1553年)","由良成繁が鉄炮好きであることを知った将軍足利義藤(後の義輝)から鉄炮一丁を贈られたのは、天文22年(1553年)です。" "宇喜多氏三老の一人で、宇喜多直家に仕え、岡山城の築城を任されたこの武将は、通称を豊前守といい、文禄の役で朝鮮に出征中に病没した。名前は何という?","岡家利","宇喜多氏三老の一人で、宇喜多直家に仕え、岡山城の築城を任されたこの武将は、通称を豊前守といい、文禄の役で朝鮮に出征中に病没した武将の名前は岡 家利(おか いえとし)です。" "この武将は、戦国時代に女性ながら城督を継ぎ、加藤清正の軍に迂回させるほどの威光を持っていたとされる女性武将です。 彼女の名前は何でしょう?","立花 誾千代","戦国時代に女性ながら城督を継ぎ、加藤清正の軍に迂回させるほどの威光を持っていたとされる女性武将は「立花誾千代」です。"
evaluating with [answer_correctness] 100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 1/1 [00:18<00:00, 18.89s/it] evaluating with [answer_similarity] 100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 1/1 [00:02<00:00, 2.04s/it] {'answer_correctness': 0.8460, 'answer_similarity': 0.8839}
なんと正確性のスコアが 0.8460に下がりました。つまり、回答は全て正解にも関わらず、1問不正解の場合よりも低いスコアということです。これは回答が冗長であったり不要な説明があることで、正解と比較したときの類似性が下がったためと思われます。
このように、定量的に評価するというのは難しいことなのです。とはいえ評価の客観性やスピードを考えると、このような評価ツールを使うやり方が今後は一般的になってくるでしょう。
100問評価させてみる
最後に、RAGASで100問を評価するのにどれくらい時間がかかるのかやってみたいと思います。
evaluating with [answer_correctness] 100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 7/7 [01:43<00:00, 14.79s/it] evaluating with [answer_similarity] 100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 7/7 [00:09<00:00, 1.41s/it] {'answer_correctness': 0.8408, 'answer_similarity': 0.8771} real 1m56.520s user 0m1.752s sys 0m2.178s
わずか2分弱です。この圧倒的な生産性を経験したら、もう人力による評価には戻れませんね。
おわりに
今回は戦国武将RAGの概要から構成、データやインデックスの準備、アプリケーション作成、評価まで一通り行いました。RAGは新しい手法が色々と出てきていますし、業務での活用も増えてくると思います。全体をざっと流したので細かいところの説明まではできませんでしたが、RAGを構築、評価する際の参考になればと思います。