🧪

LLMプロダクト開発における独自評価基準とデータセットの作り方の考察

2024/04/22に公開

プロダクトの中でLLMを使った機能を作る時に特に重要なものは評価基準データセットです。

...という、人によっては至極当たり前に感じる話ではあると思うのですが、私自身が大切にしていなかったため場当たり的なプロンプト修正に終始する経験を得ました。また、最近LLM自身にプロンプトチューニングをしてもらう方法を調べているのですが、いずれも良さそうな手法には見えるものの、きちんと評価基準とそれを自動で行う方法 or/and プロンプトのインプット/期待するアウトプットのデータセットがあることを前提としています。

しかし現実的にはプロダクト内で使う独自のプロンプトに対して、初手で「求める評価基準やデータセットを揃える」というのは中々に無理があり、これらを如何に高速に作り上げられるかということに興味が湧いてきました。
本記事では改めてそれらがなぜ大事なのかの言語化と、作っていくまでのプロセスはどんなものが良さそうかを考察していきます。

イントロダクション: LLMを使った機能をリリースするまでに必要なこと

LLMはHowなので、通常のプロダクト開発における機能と考え方は変わらないはずで「必要とされている要求が満たされているか」が確認できたらリリースできます。つまり「こんな感じのデータを前提とした時に」「こんな感じのアウトプットをLLMには返して欲しい」という期待が機能要件としてあるはずです。
この期待する振る舞いをいくつかの評価基準として定義し、その評価基準が満たせているかを確認するためには、それらに則したプロンプトへのインプットが必要なため、これを データセット として構築します。

触ったことがない方だとイメージしづらいかもしれないので具体例を書くと、次のようなプロンプトに対して評価基準は次のようなものをイメージしています。

▼ プロンプト

あなたは優秀な旅行アシスタントAIです。
{place_name}という場所の観光に関するできる限り具体的で役立つ情報を提供してください。
ただし、個人的な意見は差し控えるようにしましょう。

▼ 評価基準

- {place_name}の主要な観光スポットを網羅できているか
- 各観光スポットの特徴や見どころを具体的に説明できているか
- アクセス方法や所要時間など、実用的な情報を提供できているか
- 主観的な意見を述べていないか
- 明るく丁寧な口調を維持できているか

そしてデータセットは次のようなプロンプトの変数の部分(インプット)と期待するアウトプットのペアのリストを想定しています。

[
  {
    "input": {
      "place_name": "鎌倉"
    },
    "output": "鎌倉は神奈川県にある人気の観光地です。以下のような見どころがあります。\n\n1. 鶴岡八幡宮: 源頼朝ゆかりの神社。本殿や舞殿が国宝に指定されています。\n2. 高徳院: 有名な「鎌倉大仏」があります。高さ11.3mの銅造阿弥陀如来座像は圧巻。\n3. 長谷寺: 本尊の十一面観音菩薩は高さ9.18mの木造で、日本最大級です。\n4. 鎌倉文学館: 鎌倉ゆかりの文学者を紹介。歴史的建造物の中で作品を楽しめます。\n5. 江ノ島: 弁天橋で渡れる海に浮かぶパワースポット。展望灯台からの眺めは絶景。\n\nアクセスは、東京駅からJR横須賀線で約1時間。江ノ島電鉄や江ノ島駅からバスで各所を回るのがおすすめです。所要時間は1日あれば主要スポットを回れます。歩きやすい靴と季節に合った服装で観光を楽しんでください。"
  },
  {
    "input": {
      "place_name": "箱根"
    },
    "output": "箱根は神奈川県にある人気の観光地です。以下のような見どころがあります。\n\n1. 箱根神社: 1200年以上の歴史がある由緒ある神社。平和の鳥居や九頭龍神社が有名。\n2. 芦ノ湖: 遊覧船やロープウェイで湖上からの景色を楽しめます。海賊船も人気。\n3. 大涌谷: 箱根山の中心部にある火山地帯。噴煙や硫黄臭を体感できます。\n4. 彫刻の森美術館: 自然豊かな森の中に野外彫刻が点在。ピカソの作品もあります。\n5. 小田原城: 関東大名の北条氏ゆかりの城。天守閣からは相模湾まで一望できます。\n\nアクセスは、東京駅から小田原駅まで新幹線で約35分。そこから箱根登山鉄道で湯本駅まで行き、各所を周遊するのがおすすめです。箱根フリーパスを購入すると交通機関が乗り放題になり便利です。温泉に入るなら着替えやタオルの準備も忘れずに。"
  }
]

アウトプット例はチューニングしていくにあたってマストではないですが、

  • Few shot に応用できる
  • 関係者間で要求の具体的なイメージのすり合わせができる
  • Embedding を使った類似度評価に使える
  • DSPyなどのフレームワークでプロンプトの自動改善に使える(かも)

など様々なところで活きるので作っておいた方がいいのかなという所感です。

さて、これらを作っていかないとどうなるか、についてですが

  • チューニングする人の定性的な判断になるためLLMの振る舞いに揺らぎが出る = プロダクトにとって望ましい振る舞いで安定しない
  • チューニング自体も出てくる課題をひたすら潰す場当たり的な対応になりがち
  • 過去の評価基準がないと、その後そのプロンプトを変える時(モデルを変える時など)に何を守りたくて追加された文章なのか分からなくていじりづらい、気付いてない観点でデグレを起こす可能性がある
    • ついでに言うと過去の資産がないので再度データセットづくりを行う

などの事象が起きます。というか私は起こしました...。
ので短期的にも長期的にも評価基準データセットという資産はしっかり積み上げていく必要があります。

本記事はそれらを作っていくためにはどうしたらいいのかを考えるために

  • なぜそもそも評価はこんなに大変なんだっけ?を考える -> その性質からプロセスも規定されると考えるため
  • 評価を行っていくにあたってのHowを整理する -> 手段を理解してないと具体が考えられそうにないため
  • プロセスを考察する

という順番で書いていきます。

なぜLLMを使った機能の評価は難しいのか

最初に「なぜプロダクト開発におけるLLMの評価は難しいのか?」を言語化することによって「こういう性質を持っていて難しいから、こういう風に進めなければいけない」というのが見えてくるかなという予感がするため、まずはこちらについて想いを馳せてみようと思います。

結論から述べると以下の2つが理由なのかなと思いました。

  • LLMはインプットとアウトプットの"幅"が広過ぎるため
  • 自分たちのプロダクト独自要件に対する評価基準とデータセットは存在しない

LLMはインプットとアウトプットの"幅"が広過ぎるため

LLMの特徴は、自然言語という非構造化データをインプットとして扱える点にあり、ユーザーは自由に質問や要求を入力できます。(プロダクト内で使う場合はプロンプトは固定の部分が多いとは思いますが)
この自由度の高さがLLMの強力さである一方で、評価を難しくしている要因でもあります。

例えば会話をする用途でLLMを使っているとして、ユーザは非常に熱心に入力してくれることもあれば、AI相手には淡白なメッセージばかり送ることもあります。

この多様性ゆえに、網羅的にテストケースを用意するのは非常に困難です。どんなにたくさんのバリエーションを考えても、実際のユーザーが行うであろう入力を完全にカバーするこは不可能です。

そしてインプットの多様性だけでなくアウトプットの不確実性の高さももう一つの難しさです。
LLMは確率的な振る舞いをするため、同じ入力に対しても、毎回異なる出力が生成される可能性があります。
普段のプログラミングであれば、入力と出力の対応関係を定義し、それに基づいてテストを行えば十分でしょう。しかし、LLMの場合、ある入力に対して期待される出力が一通りとは限りません。むしろ、ある程度の幅を持った出力が生成されることが自然です。

もちろんtemperatureなどのパラメータの値を下げればある程度固定できるものの、この"揺らぎ"はクリエイティビティとも捉えられるため、用途によっては品質を犠牲にしかねません。

これらのような性質から事前に(というかリリース後でも)観点を網羅することはおよそ不可能なため、評価の観点を探索的にする必要があります。
おそらくですが、皆さんもプロンプトチューニングの際に、何度もLLMと対話している内に「うわ、こんなタイプの残念アウトプットも出てくるのか…」と感じたことは一度や二度ではないでしょう。

こうしたインプットとアウトプットの"幅"が我々がLLMを使う理由であり、評価を難しくさせている理由でもあります。

自分たちのプロダクト独自要件に対する評価基準とデータセットは存在しない

上記はLLM全般の評価についての話でしたが、プロダクト内におけるLLMの評価は「特定のインプット + プロンプトテンプレートによってできるプロンプトがタスクをうまくこなせているか」を見るものです。

至極当然の話ではあるのですが、我々が作るプロダクトには独自の要件があり、それらがそのプロダクトならではの価値を生み出します。HuggingFaceやGitHubなどには公開されたデータセットやベンチマークのコードがたくさん存在しますが、それらをそのままの形で使えるケースは非常に少ないはずです。

https://huggingface.co/docs/datasets/en/index

https://github.com/awesomedata/awesome-public-datasets

なので、我々はプロダクト内で使うプロンプトの評価基準やデータセットは全て都度自分たちで用意する必要があります。ある種新機能を考える度に"コールドスタート問題"にさらされると言ってもいいかもしれません。

ただ、ある程度は過去のデータセットや評価基準・評価の実行手法を"資産"として貯めていけば、新しい問題に対してもある程度対応はしやすくなってくるのかなと考えています。

Howについて整理する

次に、そんな性質を踏まえてどうやって評価基準とデータセットを作っていくかを考えていきたいところですが、その前に具体のHowを知っていないと、プロセスについてもあまり具体的に考えられないと思うのでこれらを一度整理してみようと思います。

最初に全体像として、LangSmithの評価の動画シリーズに個人的にすごく好きな図があるので引用します。
Landscape of personalized evals(独自評価の概観)と題して評価にまつわるものを描いてくれています。

引用: Why Evals Matter | LangSmith Evaluations - Part 1

次の4つの登場人物で構成されていると紹介しており、詳しい個々の要素については後述しますが、それぞれの中でも分解した図を作ってくれています。

  1. Dataset
  2. Evaluator
  3. Task
  4. Applying Evals

一旦この記事ではリリースまでの流れを第一スコープにするのと(リリース前にできる評価にも限界があるので、リリース後の改善フローもある程度前提にしないといけないという話はありつつも)、Task も単一にせよ複数組み合わせられるにせよRAGや機械学習でないコードなどは含まずプロンプトに絞って考えます。

なので、ここでは

  1. 評価基準とは具体的にどんなものか
  2. 評価をどうやって行うのか(Evaluator)
  3. データセットをどうやって作るのか(Dataset)

について整理していきます。

評価基準をどう作るか

LLMのアウトプットの"評価"、と一口に言っても、評価基準は様々な目的があります。
それでもいくらか分類ると考えており、まず大きくは「プロダクト要件」と「ガードレール」に大別できそうです。

プロダクト要件

読んで字のごとく、プロダクト要件に照らし合わせて、そのプロンプトが持っている役割とは何かを言語化したものです。

この中でもポジティブな要求とネガティブな要求に分かれます。

ポジティブ - 「こういう答えが返ってきて欲しい」
例えば、旅行アシスタントアプリの場合、

  • 「目的地の主要な観光スポットを網羅的に提示してほしい」
  • 「各観光スポットの魅力や見どころを具体的に説明してほしい」
  • 「アクセス方法や所要時間など、実用的な情報も提供してほしい」
    といった要件が考えられます。

ネガティブ - 「こういう答えが出ないで欲しい」
例えば、同じく旅行アシスタントアプリの場合、

  • 「主観的な意見や評価を述べないでほしい」
  • 「情報が古かったり、不正確だったりしないでほしい」
  • 「質問の意図を外れた無関係な情報を提供しないでほしい」
    といった要件が考えられます。

に分かれます。

ガードレール

若干上述したネガティブの項目に近しいのですが、プロダクト要求に(そこまで)関係なく

  • 言語は日本語か
  • 個人情報が含まれていないか
  • 卑俗語が含まれていないか
  • etc.

などなど全般的に守りたい品質について指します。
結構こちらはどんなプロダクトでも守りたいところだったりして、汎用的なライブラリが色々存在していたりします。

特に、ちょっと前に紹介記事を書いたのですが、Guardrails Hubというサイト・ライブラいのが正にそんなバリデーション関数が詰め合わせになっているので見てみてください
https://hub.guardrailsai.com/
https://zenn.dev/seya/articles/9de43ed4b260ce

非機能要求について

また、今回はLLMのアウトプット自体に対する評価とデータセットが命題なのですが、現実的にはプロンプトにはトークン数(=LLM利用にかかる費用)やレイテンシーなどの非機能要求も存在します。これらも実際の体験に影響を及ぼし、場合によってはアーキテクチャにも関わってくるため早めに意識しておくことが重要です。

以上、評価の分類について見てきましたが、感覚的にですが狩野モデルに当てはめて考えてみるとすっきり整理できる気がします。

引用: 狩野モデルから探る品質のあり方とは

ガードレールは「当たり前品質」、ネガティブは「一元的品質」、ポジティブは「一元的品質」と「魅力的品質」にまたがる、みたいな感じでマッピングされるのではないかと。前者から最低限守りつつ、データと評価を増やしながら徐々に後者の品質を満たせるようにしていくイメージです。

それでは次は、こうして作った評価基準を実際にどう運用していくか、評価の方法について見ていきたいと思います。

評価はどうやって行うか

評価基準ができたとして、次に具体的にどうやって評価を行うかが課題となります。考えることとしては「評価の具体的なアウトプットの形はどうなるのか」と「それを誰がどのように行うのか」の2点があります。

評価の具体的なアウトプットの形はどうなるのか

評価の具体的なアウトプットは、データセット内の各データに対してLLMを実行した結果に、評価基準を適用した結果として得られます。

理想的には、評価結果は数値で表現されることが望ましいでしょう。例えば、0から1の間の数値でスコアリングする方法があります。これにより、複数のプロンプトや設定の結果を比較したり、時系列での変化を追跡したりすることが容易になります。

ただし、評価基準によっては、数値化が難しい場合もあります。そのような場合は、真偽値(True/False)を用いて、評価基準を満たしているかどうかを表現することもあります。例えば、「出力に特定のキーワードが含まれているか」といった基準は、真偽値で表現するのが適しています。

また、評価結果は、個々のデータに対する結果だけでなく、データセット全体に対する集計値としても表現されることが一般的です。例えば、数値化された評価結果の平均値や中央値、真偽値の割合などです。これにより、プロンプトやシステム全体の性能を総合的に判断することができます。

例として下記はLangSmithで評価を実行した時の結果画面です。

誰が評価するのか

大きくは次の3つがあります。

  • 人間が行う
  • LLMが行う(LLM-as-a-judge)
  • プログラマブルに行う

主にはお金 アノテーションの品質 時間 の要素を天秤にかけて選んでいきます。

人間が行う

一番お金も時間も使いますが、アノテーションの品質の信頼が高くなるところです。
おそらく最初は社内のプロダクト開発に関わる人が行うことが多いと思います。

もしくはアノテーションをする人を雇ったりすることもあります。ちょっと私は雇ったことがないのでどこで雇うといいかの知見はないのですが、下記の本にて Amazon Mechanical Turk というサービスで依頼ができるというのを学んだことはあります。

https://www.amazon.co.jp/ゴースト・ワーク-メアリー・L・グレイ/dp/4794973489

https://www.mturk.com/

どんな形でアノテーションを依頼するにせよ、認識すべき重要なこととして「その人はそのタスクのアノテーションに相応しい人か」という観点があります。
分かりやすいのがドメイン特化のプロダクトを作る場合で、例えば医療系のサービスにLLMを使うときのアノテーションに、ずっとプログラミングだけやって生きてきた私は間違いなく不適当でしょう。
そこまでドメイン知識が深く求められるものでなくとも、社内の人々やアノテーションをお願いする人たちがユーザとドンピシャな層でなかったり、同じような課題をイメージできないことは多くあるでしょう。

LLMが絡まないプロダクト開発であってもドメインエキスパートと協力してより良いプロダクト要件や設計を探っていくことはありますが、アノテーションに関しても同様であると考えられます。

どんな要件にLLMを使うか次第で要求度は変わってきますが、高品質のアノテーションができる人と、できればアノテーションだけでなく理由もセットで教えてもらいながら評価基準を作っていく体制が作れるかが鍵になってくるのかなと思います。

LLMが行う(LLM-as-a-judge)

LLM-as-a-judge も最近かなり使われている手法です。
文字通りLLMに評価をしてもらって「0-1.0の間でスコアをつけてください」という形で数値を付けてもらったり Yes or No の二値判定をしてもらって評価してもらいます。
例としては次のようなプロンプトで行います。

質問文:
{question}

回答文:
{answer}

以上の質問文と回答文のペアについて、以下の観点で0から1の間の数値で評価してください。数値以外は出力しないでください。

- 回答は質問の意図を的確に捉えているか

プログラマブルに判定することが難しいような評価基準でもプロンプトを書くだけでシュッと評価を実現できるのが強みです。

上記の評価の仕方は一番シンプルな方法で、大体"LLM-as-a-Judge"というフレーズが使われる時はこのやり方を指すことが多いと思いますが、こちらはスコアベース評価と呼ばれるそうです。
その他にも

  • 確率ベース評価: 評価対象のアウトプットが生成される確率(生成尤度)を算出し評価スコアとする
  • ペアワイズ評価: 1つのタスクに対して2つの出力結果を比較していく

などなど一口に"LLM-as-a-Judge"と言っても様々な方法があるので用途・目的に応じて検討していけると良さそうです。ちなみに私はスコアベース以外は全部下記の記事で知ったので、ご興味ある方は是非読んでみてください。

https://www.brainpad.co.jp/doors/contents/01_llm_as_a_judge/

ただLLMも次のようなバイアスを持っていたりして注意が必要です。

  • 位置バイアス
    • LLMが特定の位置の回答を優遇する傾向がある(主に最初と最後が優遇されやすいなど)
    • 下記論文内では2つの回答の順序を入れ替えてLLMに2回判定させ、両方で一方の回答が優れていると判定された場合にのみ、その回答を優れていると判断する方法が提案されている
  • 冗長性バイアス
    • LLMが、明確さ、質、正確さが劣っていても、長く冗長な回答を優遇する傾向がある
  • 自己高揚バイアス
    • LLMが自身が生成した回答を優遇する可能性がある

https://arxiv.org/abs/2306.05685

また、人間より安いとは言え、お金もなんだかんだかかるので、闇雲に全てをLLMに評価させて、多様なインプットで評価をぶん回すことも現実的ではありません。

  • 評価のイテレーションを回し始める初期に作り始めて、適度にエラーを見つける参考にしつつ、評価のプロンプトも品質を上げていく
  • 様々なプロンプトで汎用的に活きる評価基準でありそうなら、評価プロンプトの評価もちゃんと時間をかけて行い信頼できるものに育ててから流用する

みたいな形での運用ができるといいのかなぁと考えています。

ちなみに「評価のプロンプトが本当に正しく評価できているのか?」という問いも当然のことながらもたれると思います。私もみんなどうやっているのか疑問に思いちょっと調べてみたのですが、思ったより悪く言えばテキトーというか、素直に自分が思う評価の文言で始めているようです

例えば、以下のようなプロンプトを使って評価を始ます。

次の文章は、旅行先のおすすめスポットを紹介する文章です。この文章が、読者にとって役立つ情報を提供できているかどうかを5段階で評価してください。5が最も役立つ、1が全く役立たないとします。

評価の観点:
- スポットの特徴や見どころが具体的に説明されているか

評価対象の文章:
'''
{ここに評価対象の文章が入ります}
'''

評価スコア:

このようなプロンプトから始めて、実際に様々な文章を評価させてみます。そして、評価結果を人間の感覚と照らし合わせ、過不足ない評価ができているかを確認します。それで繰り返し実験を回す中で、ちゃんとエラーを検出してくれているな、とか、偽陽性が多いな、など感じるところがあればプロンプトを調整したり、評価の観点を追加・修正したりしながら、徐々に評価の質を高めていくイメージです。

評価は最終的な品質保証という目的も大きいのですが、実験中のエラーの検出を手助けすることにより開発サイクルを速くするという目的もあります。その目的に照らすと、最初は評価の精度自体にこだわり過ぎず参考程度から始めていくのがいいのかもしれません。

また、そのプロダクトにとって色んなところで出てきそうな評価項目であれば一定の時間を投資して**"評価の評価"**を行うのも一手かなと思います。

プログラマブルに行う

一番安いし時間もかからない方法で、評価項目がこれで評価できるのであれば積極的に採用すべき手法です。

簡単なものだと文字数の長さを判定するとか、特定の文字列が含まれていないかを見るだとか、これはLLMと同列に考えるべきかもしれないですが、感情分析のような機械学習モデルを使って判定できるものなどを想定しています。

https://huggingface.co/blog/sentiment-analysis-python

人間はもちろん高いですし、LLMもまあまあ高いので、最初はお手軽に試せるLLMで初めて、有力な評価項目だと感じたらプログラマブルに代替できないか検討する、みたいなフローでも考えていけるといいのかなと思います。

データセットはどうやって作るか

次に評価を行うために必要なデータセットを作る方法について見ていきます。

  • 人間が作る
  • LLMなどに作ってもらう(Synthetic Dataset)
  • 実際の実行ログから集める

また、前提としてなのですが、どのような手法を取るにせよ、データセットを作る目的は評価をするためであるため、評価基準を満たすような良い例と、ガードレールに引っかかるような悪い例を意識的に含めることが大切です。

人間が作る

やはり人間が作ることが第一候補に上がるでしょう。
手動でプロダクトの要件を満たすような典型的な入力を複数パターン用意し、それぞれに対して期待される応答を作成します。
少なくとも最初の数個は開発者やプロダクトマネージャーの間で「こんなアウトプットが望ましいよね」という共通認識を持つためにも、要件に照らし合わせて作ってみるのがいいのではないかなと思います。

ただ、評価のためには同じ評価観点に対しても複数のインプットでテストされることが望ましいです。これを実現するのに全て手動で十分な量のデータを作るのは中々骨の折れる作業なので、そんな折に次のLLMによる生成が検討にあがります。

LLMなどに作ってもらう(Synthetic Dataset)

データセット作成にもまた LLM の出番です。
ちなみにLLMなどで自動で作ったデータのことをSynthetic Dataと呼ぶそうです。
https://huggingface.co/blog/synthetic-data-save-costs

これは私も最近試しているのですが、プロンプトの文字列だけ渡して「様々なバリエーションのデータを作ってください」というプロンプトだけだとそこまでいい感じのデータセットが作れる印象はないのですが(単語だけ変えたものが出てきたりする)、評価基準を与える or/and まず考えてもらってから、それをテストするようなデータを出して、と伝えると割りかし評価に使える品質のデータを出してくれます。この時Few-shot learningの要領で、人間が作った典型例を数個提示するのもフォーマットなどがズレすぎなくて効果的です(多少引きづられ過ぎる感を感じることもありますが)。

まだチューニングの余地はありますがイメージとしては下記のような感じです。

データ生成プロンプトの例

▼ 評価基準生成のプロンプト

given prompt below, this prompt is aimed to {purpose_of_prompt}
list what perspectives is necessary to evaluate this prompt in addition to the initial evaluation perspectives.

<evaluation_perspectives>
{evaluation_perspectives}
</evaluation_perspectives>

<prompt>
{prompt}
</prompt>

<examples>
{examples}
</examples>

▼ データ生成のプロンプト

now create 3 examples of input and expected output that can test each evaluation perspective.

write only list of examples JSON

また、プロンプトで作る以外にも CLI や実際の画面で、E2Eテストツールなどと組み合わせてインプットはLLMに考えてもらいながら何度も回す、という手法も考えられます。

特にデータがそんなにない初期のプロンプトチューニング・評価をするタイミングで活躍しやすい手法なのではないかなと考えています。

また、最近人から教えてもらったのですが、モデルのチューニングのためにInstructionデータセットを作る分野でもSynthetic Dataの活用の深掘りは進んでいるらしく、先にあげたようにSeedとなる例から増やす方法や、突然変異を起こす手法などが提案されています。

https://arxiv.org/abs/2212.10560
https://arxiv.org/abs/2304.12244

引用: Synthetic Data (Almost) from Scratch: Generalized Instruction Tuning for Language Models

これらは学習用のデータ作りがゴールのためドンピシャで同じではないかもしれないですが、今回のプロンプト用のデータセット作成にも考え方はそのまま応用できそうで、例えばEvolve-Instructのバリエーションを広げる方法は開発者が事前に認識できていなかった評価観点まで広げられるんじゃないかと考えています。

実際の実行ログから集める

そして最後は「実際のログからデータを集める」です。
これは社内で回す検証からもそうですし、実際のユーザに触ってもらった中で溜まったログも含みます。

実際のユーザーの入力と出力のペアを収集することで、開発者が想定していなかった多様なケースをカバーすることができます。特に、プロダクトをリリース後、一定期間運用してデータを蓄積することで、より現実に即したデータセットを構築できるでしょう。

ただし、ログからデータを収集する際には、ユーザーのプライバシーに十分配慮する必要があります。個人情報の扱いには細心の注意を払い、適切な匿名化処理を施すことが求められます。

収集したデータは、評価基準に照らし合わせて人手、もしくは先ほど紹介したようにLLMなどを使ってアノテーションを行うことになります。何でアノテーションするにせよ「対象を選ぶ(ユーザのフィードバックと結び付けられているとなお良し)」「アノテーションすると自動でデータセットに追加される」などのパイプラインを作っていくのが重要な取り組みになってきます。

また、そもそもの大前提として、これができるようになるためにはLangSmithなどのロギングツールを導入していることが前提となります。それらのツールは先にあげたようなアノテーションキューやフィードバック機能、自動でのスコアリング機能など様々なサポートを持っていることが多いため、技術選定の際にはそういった観点を気にしていけるといいのかなと思います。

再三の話にはなりますが会社内で開発に携わる人たちだけで評価観点を網羅する、データセットを作り切る、ということは基本的に不可能なので、こういった生のデータを継続的に改善に用いることは非常に重要な取り組みとなります。

評価基準とデータセットをどう管理していくか

以上、具体的なHowについて見てきたのですが、もっともっと具体的に評価基準やデータセットはどのように管理するのか?についても見ていきます。

ちょっとこの記事はあまり特定の技術依存にならない抽象的な内容にしようとは心がけていたのですが、具体のイメージを持てた方がいいと思うので、ここでは最近使っているLangSmithでの事例を紹介させてください。

LangSmithには評価機能とデータセット機能が存在します。

データセット機能では次のように特定のプロンプトに対するインプットとアウトプットのExampleたちを管理することができます。ちなみにですがLangSmithはロギングの機能もあり(というかこっちがメイン)、ログから Add to Dataset をポチッと押すだけでこちらのデータセットに追加することができるので、検証やリアルユーザとのインタラクションからデータセットを補強していく時に便利です。

評価はLangSmithのSDKを用いてコードで実行します。
こうしたコードがテストコードのようにメンテされていくといいのかなと考えています。

from langsmith.schemas import Run, Example
from langsmith.evaluation import evaluate

# Define dataset: these are your test cases
dataset_name = "langgraph-test"

def predict(inputs: dict) -> dict:
    # ここでLLM実行したりして結果を返す
    # inputs にはデータセット内の一行が入ってくる = この predict 関数はデータセット内のExampleの数だけ実行される。
    return {"output": response}

# 評価を実行する関数、evaluatorsはリストを取るので複数の評価関数を定義できる
# {"key":"結果画面で見たいラベルの名前", "score": 数値 or True/False で表現する}
def must_mention(run: Run, example: Example) -> dict:
    prediction = run.outputs.get("output") or ""
    required = example.outputs.get("must_mention") or []
    score = all(phrase in prediction for phrase in required)
    return {"key":"must_mention", "score": score}

experiment_results = evaluate(
    predict,
    data=dataset_name,
    evaluators=[must_mention],
)

このコードを実行するとLLMの実行及び評価が実行され、先ほどのデータセットのページ内にある "Experiments" というタブ内から結果が見られるようになります。(エラー吐いてて内容空ですが、アウトプットには実際の実行結果が含まれます。雰囲気だけ掴んでください)

▼ 実験結果一覧画面

▼ 実験詳細

ちなみにのLangSmithの推しポイントなのですが、単一のプロンプトだけでなく、複数のChainやLangGraphなどのトレースも評価と紐づけて見られるのが魅力的です。

例として下記はLangGraphのトレースで、これのおかげでLangGraphでシステム全体の処理を実行 <=> 個別の問題を見つけたらそこだけユニットテスト、を行ったり来たり、というようなワークフローが取りやすくなります。

これが強力過ぎて最近はもう積極的にLangChainエコシステムにロックインされにいくのがいいんじゃないかという気持ちになっています。

ただ、今回は例としてLangSmithをご紹介しましたが「データセットと評価基準、その実行コードが共通資産としてメンテナンスされていく」という部分が重要で、そこが担保されるのであれば他の技術でもいいのかなと考えています。

プロセスを考える

以上、具体的なHowについて見てきました。
「これらを組み合わせてあとはやるだけ!」と行きたいところですが、先ほどの「なぜLLMを使った機能の評価は難しいのか」で述べた通りゴールとすべき評価基準を一度に定義しきるのは非常に難しいです。

なので、どうやって「段階的に評価基準とデータセットを作っていくのか」のプロセスについて想いを馳せてみようと思います。ちなみにですが、一応考えてはみるものの結局行ったり来たりで泥臭くはなるんだろうなという予感がしています。

改めてですが、機能リリースのジャッジをするまでは「評価基準とそれに紐づく評価データセットがあり、その評価データセットに対してLLMを実行し、事前に定めた threshold を超えることが確認できる」がゴールになります。

なので、まず最速で評価基準とデータセットを作ることを考えてみるのですが、再三申し上げている通りこれらを作るプロセスは探索的にならざるを得ない(おそらく)ため ad hoc にプロンプトチューニングしながら、という前提がついて回るでしょう。

大まかですが以下のような流れになるのではないかと思います。

  • 初期バージョンの評価基準とデータセットを作る
  • アーキテクチャ設計
  • 全体と個別のプロンプトをいくらか手動で回す
  • 個々のプロンプトのデータセットと評価基準を作る(単体テスト)
  • 全体を通して確認する(結合テスト)

初期バージョンの評価基準とデータセットを作る

まずは手動で要求から落として評価基準を作ります。
要求がかなり具体的であれば別ですが、実際のシステム回しながらの方が発想が広がりやすいことも多いと思うので、ここではあまり厳密に作り上げようとせず、PoCテスト時のデータを作るくらいの感覚で作ります。

初期アーキテクチャ設計

作りたい機能に対して一個のプロンプトで十分、なんてことはあまりなく、何かしらのビジネスロジックだったり、複数のプロンプトが連なったり、分岐が挟まったりします。
この「なんとなくいけそう〜」の実感を得るのがこのフェーズになります。

ただし、機械学習が絡むシステムは PoC 貧乏なんてフレーズがあるくらい「お試しで作ったものが後から検証すると想像以上に残念だった」ということがよく起きます。

多いのは、まずはPoC(Proof of Concept:概念実証)でお試し版をつくり、うまくいったら本番のプロジェクトに移るというケースですが、これにも問題があります。それは「PoC 貧乏」に陥る例があちこちで散見されることです。帰納型ソフトウエアの特徴として、100%の正解を保証できないという点が挙げられます。発注者が後から「精度が足りない」「能力が不十分」などと指摘できてしまうわけです。

https://www.nii.ac.jp/today/81/1.html

これは技術的な話というよりはマインド的な話なのですが、この作業者による定性の「なんとなくいけそう」から実際のプロダクション品質に持っていくまでに想像以上に大きな乖離があることを認識して油断せずに評価を詰めていくのが大事なのかなと感じています。

全体と個別のプロンプトをいくらか手動で回す

一定枠ができたら、いきなりかっちり評価基準とデータセットを作るというよりは、いくらかシステムと戯れて評価観点への理解を深めたり、またその過程でデータも増えていくと思うので前準備としてモンキーテストのような感覚で手動で回すのがいいのかなと考えています。

まずは全体のフローを通してみて、各プロンプトの振る舞いや、プロンプト間の連携がうまくいっているかを確認します。その際、様々な入力パターンを試してみることで、システムの頑健性や境界条件の扱いなどの課題が見えてくるはずです。

また、個々のプロンプトに対しても、評価基準の観点からテストケースを作成し、期待される動作をしているか入念にチェックします。この手動テストを通じて、評価基準の過不足や、データセットに必要なバリエーションなどが明らかになってくるでしょう。

個々のプロンプトのデータセットと評価基準を作る(Unit tuning)

手動テストで得られた知見を基に、個々のプロンプトの評価基準を精緻化していきます。評価基準は可能な限り定量的で測定可能なものとします、が、ある程度は定性的にしか判断できない(定量で判断しようとすると、その仕組みを作るのに多大な時間がかかる)ものもあるとは思うので、そこは"要はバランス"です。

その上で、評価基準を満たすデータセットを拡充していきます。前述の通り、LLMやユーザ(最初は社内の人)ログなども活用しつつ、品質の高いデータを効率的に収集することが肝要です。

データセットができたら、それを使ってプロンプトのチューニングを行います。評価基準の数値が上がるまで or 定性的に見て課題が出なくなったと感じるまで行いますが、この過程でも評価基準やデータセット、評価の仕方を更新したくなると思うので適宜アップデートしていきます。


この時にいわゆる実験管理環境があるのが大事

ただし、タスクの重要度や変更の可能性によっては、厳密な評価基準とデータセットの作成が必ずしも必要ではないケースもあります。例えば、軽微なタスクや、プロトタイプ段階で大きく変更される可能性が高い部分などです。

とは言え、後から参照できる資産として少量でもデータや評価の観点を残しておくことは重要です。手元で確認するだけでなく、チーム内で決められた場所に保存しておくことで、後続の開発や改善に活かすことができるでしょう。

評価基準とデータセットの作成には一定のコストがかかりますが、長期的な品質向上とメンテナンス性の観点から、適切な範囲で実施することをおすすめします。

全体を通して確認する(Integration Test)

個々のプロンプトの品質が上がったら、再度システム全体の動作を確認します。個別には問題なくても、組み合わせた時に新たな課題が出てくることは珍しくありません。全体の結果を見る方法はロギングツールで一気通貫で見られるのであればそれを活用したり、場合によってはUIを触ったり専用のツールを作ったり様々です。


LangGraphの一連の処理の流れが見られる図

この「全体を通したテスト <=> 単体テスト」を行ったり来たりするのがチューニングの流れになるのかなと思います。

新たな問題が出てこなくなり、作り上げてきた評価基準やデータセットに対して「ここまでクリアできたらOK」というthresholdを定義し、それを超えたら、いよいよリリースに向けて準備を整えていくことになります。ただし、リリース後も継続的なモニタリングと改善は欠かせません。実際のユーザ feedbackを反映しつつ、評価基準とデータセットのアップデートを継続的に行っていく必要があるでしょう。

おわりに

以上、こんな感じで評価基準とデータセットを作っていけるといいのではということを考えてきました。皆さんの現場ではどんな形でやっているかなどコメントいただけたら嬉しいです。

なんとなくですが、こういったプロンプトのための評価基準やデータセットのメンテナンスは、フロントエンドやE2Eのテストと同様に、「書いた方がいいとわかっていても、メンテナンスされなくなりがち」という傾向が発生する予感がしています。プロンプトチューニングは、目の前の課題に応じてどんどんプロンプトを変更していく方が、評価やデータセットなどの周辺物をコツコツ作るよりも進捗を感じやすいからです。

しかし、短期的には進捗が遅くなるように感じられても(この感覚もおそらく多くの場合間違ってるとは思うのですが)、長期的に見れば、適切な評価基準とデータセットを整備することが、生産性と品質の向上につながります。評価基準があることで、プロンプトの品質を客観的に測ることができ、改善のポイントが明確になりますし、良質なデータセットは、プロンプトの汎化性能を高め、様々な入力に対して安定した出力が得られるようになります。

ただ、評価基準とデータセットの重要性を理解していても、実際に作成・メンテナンスするのは容易ではありません。実際に場合によっては作ることがペイしないケースも存在すると思うので柔軟さが必要です。
一方で、重要性の啓蒙活動だったり、作成のハードルを下げる環境づくりも必要になってくるのかなと感じています。例えば、評価基準のテンプレートを用意したり、データセット作成を補助するツールを整備したり。

というところで私個人としては今後は次のようなことに取り組んでいきたいなと考えております。これらも別途発信していきたいものです。

  • 評価、データセット、実験管理環境構築
  • プロンプト Example の Synthetic Data
  • 評価基準とデータセットから自動プロンプトチューニング

最後に、自分の実感としてこういったLLMを使ったプロダクト開発におけるオペレーション周りの知見はあまり出回ってこないな〜というのを感じています。純粋にまだまだ人口が少ないというのもあると思うのですが、そういったことをシェアできる場があると楽しそうだなと思い、大々的にイベント開くかとかまでは考えていないのですが小さなコミュニティから作っていきたいなと考えています、数人話せる人がいるだけでも大分嬉しい。
まずはオンラインの雑談からでも良いので、いわゆるLLMOpsに興味ある方、ぜひお気軽にコメントか下記よりDMいただけると嬉しいです。

https://twitter.com/sekikazu01

それでは、お読みいただきありがとうございました!👋

参考文献

LLM App の評価

https://hamel.dev/blog/posts/evals/

https://docs.smith.langchain.com/evaluation

https://docs.smith.langchain.com/evaluation/recommendations

https://www.confident-ai.com/blog/how-to-evaluate-llm-applications

https://humanloop.com/blog/evaluating-llm-apps

From MVP to Production // Day 2 Panel 2 // AI in Production Conference

Evaluating LLM-based Applications // Josh Tobin // LLMs in Prod Conference Part 2

Synthetic Dataset

https://speakerdeck.com/kunishou/da-gui-mo-yan-yu-moderukai-fa-notamenori-ben-yu-instruction-detasetutozuo-cheng-noqu-rizu-mi?slide=23

https://zenn.dev/kendama/articles/9092e0b4ed09ca

https://arxiv.org/abs/2402.13064

https://www.marktechpost.com/2024/04/13/google-ai-introduces-codeclm-a-machine-learning-framework-for-generating-high-quality-synthetic-data-for-llm-alignment/

https://arxiv.org/abs/2404.07503

Discussion