LLMの要約結果を評価する
この記事は Magic Moment Advent Calendar 2024 7 日目の記事です。
Magic Moment の @tdoi です。
2024年1月にジョインし、Magic Moment の製品開発を担当しております。
学生時代に、マルチエージェントシステムに関する研究に従事していました。
昨今、LLM の躍進によって、エージェントという言葉を見聞きすることが増えて少しそわそわしています。
はじめに
GPT を始めとする LLM の性能の向上は著しく、ここ1、2年は、LLM をいかに活用するかを検討されてきた方も多いのではないでしょうか?
LLM の出力が期待以上であって驚くことも多い一方で、出力が不適切であることもあります。
対話的に利用している際はその出力を確認し、適切な判断のもとで活用すればよいのですが、 システムに組み込んで利用する際には、その出力結果の妥当性には注意を払う必要があります。
LLM の出力結果の妥当性については、標準化された方法が確立されたものということではありませんが、様々な研究がされてきています。
その方法論の1つに、LLM-as-a-Judge と呼ばれる手法があります。ようは、LLM にその評価までさせてしまおうというものです。
LLM-as-a-Judge そのものに対する情報については、LLMによるLLMの評価とその評価の評価についてという記事が分かりやすくまとまっており、本記事の執筆にも参考にさせて頂いております。
本記事では、LLM-as-a-Judge を、文章から目的の情報を抜き出すというタスクに注目して、 スコアベースの手法を実施してみた結果を共有します。
想定するタスク
LLM には大きく分けると、2つの利用方法があるかと思います。
1つは、LLM が学習した知識を取得すること、もう1つは、入力した情報に対して処理を行うことです。
前者は、「アジャイル開発とは何ですか?」という問いに対して、
「アジャイル開発とは、ソフトウェア開発の手法の一つで、従来の「ウォーターフォール型」と呼ばれる開発手法とは対照的なものです。」
といった回答が返ってくるようなケースです。
このような利用方法も有効なのですが、AI エージェントという言葉を視野に入れると、後者の使い方の妥当性が重要になります。
そのため、本記事では後者の形式のタスクについて扱いたいと思います。
後者の利用方法は、「アジャイルマニフェストの4つの価値基準と12の原則の文章と、その内容に基づいて重要な要素を100文字程度で出力してください。」という依頼を実施するようなケースです。
一例にはなりますが、Gemini に処理をさせると以下の回答が得られました。
個人と対話、動くソフトウェア、顧客との協調、変化への対応を重視。計画より柔軟性を、ドキュメントより実働を重視。顧客満足度を最優先に、短いサイクルで価値を届け、チームで協力し改善を続ける。シンプルさを追求し、自己組織化を促す。
では、この結果が妥当かということを考えると、 どう評価するかによって結果は変わってきます。
コンセプトを伝えるという意味であれば、 4つの価値基準のキーワードが含まれていて、概要は十分含まれていると思います。
しかし、12の原則という意味では、情報量が大分、欠如してしまっているとも言えます。
このようなタスクに対して、人が判断して含まれるべき要素を明示的に指定して、LLM-as-a-Judge の一例としてスコアベースの評価するのが本記事の内容です。
テストケース
今回対象とするタスクは、情報源となる文章に対して、
その文章から抽出したい情報を取り出す問いと、その問いの結果として含まれることが期待される要素のリストとしてテストケースを設定することとしました。
もちろん、どのような要素が含まれるべきかも含めて LLM が判断できることを検討していきたいです。
ただ、まずは人が期待した要素を含んでいるかを評価したいと思います。
この目的となる評価において、テストケースを次の2つのファイルで管理することとしました。
input.txt
元となる情報源のテキストファイルです。
上記の例で言えば、アジャイルマニフェストの文章が記載されているファイルです。
test_cases.json
テストケースのリストが記載されているファイルです。
以下のような構造にしました。ここでは、4つの価値基準の要素を含むことを期待したテストケースとしています。
[
{
"question": "重要な要素を100文字程度で出力してください。",
"expected": [
"個人と対話",
"動くソフトウェア",
"顧客との協調",
"変化への対応",
]
}
]
評価の考え方
今回は、テストケースの評価として以下の2つの観点で評価することとしました。
- 期待される要素が含まれているか
- 期待される要素以外の要素が含まれていないか
上記の例で言えば、次の5つの観点で評価します。
- 個人と対話という要素が含まれているか
- 動くソフトウェアという要素が含まれているか
- 顧客との協調という要素が含まれているか
- 変化への対応という要素が含まれているか
- 上記以外の要素が含まれていない
各観点を、0から1のスコアで評価し、それらの平均値を最終的なスコアとしました。
実装
LangChain を利用して、GPT-4o のモデルで動かしてみます。
LLMへの問い合わせ
まず、テストケースを管理する Class と、 LLM からの出力を取得するための Class を用意します。
class TestCase:
def __init__(self, question, elements):
self.question = question
self.elements = elements
class Answer(BaseModel):
number: str = Field(description="question number")
question: str = Field(description="question")
answer: str = Field(description="answer to the question in Japanese")
class AnswerList(BaseModel):
answers: list[Answer] = Field(description="list of answers")
def to_json(self):
return json.dumps([answer.model_dump() for answer in self.answers])
@staticmethod
def from_json(json_str):
json_data = json.loads(json_str)
return AnswerList(answers=[Answer(**answer) for answer in json_data])
以下の関数において、テストケースに基づいて LLM に問い合わせ、その結果を取得します。
なお、実装には LangChain を利用し、モデルとして GPT-4o を指定しています。
def make_summary(text: str, test_cases: list[TestCase]) -> AnswerList:
questions = "\n".join([f'{i}. {question}' for i, question in enumerate([test_case.question for test_case in test_cases], 1)])
query_template = '''
以下の文章を元に、それに続く要素についてまとめてください。
# 文章
{text}
# 質問
{questions}
# 回答方法
{format_instructions}
'''
output_parser = PydanticOutputParser(pydantic_object=AnswerList)
prompt = PromptTemplate(
template=query_template,
input_variables=["text", "questions"],
partial_variables={"format_instructions": output_parser.get_format_instructions()},
response_format={"type": "json_object"})
model = ChatOpenAI(openai_api_key=os.getenv("OPENAI_API_KEY"),
model="gpt-4o").bind(response_format={"type": "json_object"})
chain = prompt | model | output_parser
result = chain.invoke({"text": text, "questions": questions})
return result
これで、AnswerList のオブジェクトとして、テストケースで設定した問いに対して、LLM からの出力を取得できます。
スコアの算出
構造的にほとんど同じなのですが、LLM の評価を受けとるための Class を用意します。
class EvalScore(BaseModel):
point: str = Field(description="point view of the evaluation")
score: float = Field(description="score")
explanation: str = Field(description="explanation of the score in Japanese")
class Evaluation(BaseModel):
question: str = Field(description="question")
answer: str = Field(description="answer")
scores: list[EvalScore] = Field(description="list of evaluations")
その上で、以下の関数を定義し、LLM の結果とテストケースで指定された期待されるものを比較し、スコアを算出します。
def eval_test_case(text: str, test_case: TestCase, answer: Answer):
check_points = [f'「{answer.answer}」という回答には、「{element}」という内容が含まれていること' for element in test_case.elements]
check_points.append(f'「{answer.answer}」という回答には、上記以外の内容が含まれていないこと')
check_points = "\n".join([f'{i}. {point}' for i, point in enumerate(check_points, 1)])
evaluation_method = f'''
評価は、評価方法に記載する観点ごとに評価をし、それぞれ0(妥当ではない)から1(妥当)までの数値でスコアをつけてください。
{check_points}
'''
query_template = '''
以下の文章を元に「{question}」 という問いに対する回答として、「{answer}」という回答の内容を評価してください。
# 評価方法
{evaluation_method}
# 情報元の文章
{text}
# 回答方法
{format_instructions}
'''
output_parser = PydanticOutputParser(pydantic_object=Evaluation)
prompt = PromptTemplate(
template=query_template,
input_variables=["text", "question", "answer", "evaluation_method"],
partial_variables={"format_instructions": output_parser.get_format_instructions()},
response_format={"type": "json_object"})
model = ChatOpenAI(openai_api_key=os.getenv("OPENAI_API_KEY"),
model="gpt-4o").bind(response_format={"type": "json_object"})
chain = prompt | model | output_parser
result = chain.invoke({"text": text, "question": answer.question, "answer": answer.answer, "evaluation_method": evaluation_method})
return result
ソースコード
ソースコード全体は以下の Gist にて公開しています。
Gist
結果
実際に、Scrum ガイドを文章として、いくつかの質問をテストケースとして設定しスコアを算出してみました。
ここでは、期待する要素として単語の例を示していますが、期待する内容を文章で入力しても同様にスコアを算出します。
Q: スクラムの理論の元になっている概念とは?
A: スクラムの理論は「経験主義」と「リーン思考」に基づいています。経験主義では知識は経験から生まれ、意思決定は観察に基づくとされています。リーン思考では、ムダを省き、本質に集中することが重視されます。
Expected: ['経験主義', 'リーン思考', '透明性', '検査', '適応']
Average Score: 0.5
Q: スクラムの価値基準とは?
A: スクラムの価値基準は、確約(Commitment)、集中(Focus)、公開(Openness)、尊敬(Respect)、勇気(Courage)の5つです。これらの価値基準は、スクラムチームの作業・行動・振る舞いの方向性を示し、決定や手順、スクラムの使用方法を強化するものとされています。
Expected: ['確約', '集中', '公開', '尊敬', '勇気']
Average Score: 1.0
Q: スクラムにおけるロールとは?
A: スクラムにおけるロールは、開発者(Developers)、プロダクトオーナー(Product Owner)、スクラムマスター(Scrum Master)の3つです。開発者はスプリントでの作業を担当し、プロダクトオーナーはプロダクト価値の最大化を担い、スクラムマスターはスクラムの実践と効果的な進行を支援します。
Expected: ['プロダクトオーナー', 'スクラムマスター', '開発者']
Average Score: 1.0
最初のテストケースでは、Scrum ガイドの理論の章の直後にある「経験主義」と「リーン思考」については言及されましたが、その後の3本柱の言及はありませんでした。
そのため、結果のスコアは 0.5 となりました。
また、LLM の出力を制御することは難しいので、入力するテキストを変更してスコアの妥当性も検証しています。
Scrum ガイドの価値基準の部分までを文章とし、価値基準から公開と尊敬の部分を削除した入力に変更したところ、
期待通り後半2つの質問の結果は以下のようになりました。
Q: スクラムの価値基準とは?
A: スクラムの価値基準は次の5つです:確約(Commitment)、集中(Focus)、勇気(Courage)。これらはスクラムチームの作業や行動の方向性を示し、意思決定や手順、スクラムの使用方法を強化するものです。
Expected: ['確約', '集中', '公開', '尊敬', '勇気']
Average Score: 0.6666666666666666
Q: スクラムにおけるロールとは?
A: 質問文の文章にはスクラムにおけるロールについての具体的な説明は含まれていません。
Expected: ['プロダクトオーナー', 'スクラムマスター', '開発者']
Average Score: 0.25
おわりに
汎用的に利用できるようにするには、もう一工夫必要だと思いますが、評価を始める一歩目としては使えるかなと思います。
実際、この記事を書くにあたっても、テストケースの質問文は、これらの評価を参考に調整しながら作成しています。
AI エージェントの活躍が期待されている中で、LLM の結果の評価は重要な課題です。
このような一例であっても何かの参考になれば幸いです。
次回のアドベントカレンダーは t-miyakさん の 「Atlantis で安全かつ効率的に Terraform の組織運用を実現する」 です。お楽しみに!
Discussion