プロンプトエンジニアリングを加速する、AI モデルの性能評価フレームワーク「OpenAI Evals」を試してみた

2023.03.15

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは。CX 事業本部 Delivery 部のきんじょーです。

先日 ChatGPT の API が公開されたかと思いきや、今度は GPT-4 が公開されて大盛り上がりですね。 GPT-4 の公開とともに、AI モデルの性能を評価するためのフレームワーク「OpenAI Evals」が公開されていたので、早速試してみました。

OpenAI Evals とは?

README.mdによると以下の記載があります

Evals は、OpenAI モデルを評価するためのフレームワークであり、ベンチマークのオープンソースレジストリでもある。 Evals を使用して、以下のような評価を作成し実行することができます。

  • データセットを使ってプロンプトを生成する。
  • OpenAI モデルが提供する補完の品質を測定し
  • 異なるデータセットやモデル間で性能を比較することができます。

モデルに与えるプロンプトと、期待する結果を json 形式のデータセットにし、モデルの回答精度を計測したり、異なるモデル間で比較ができるようです。

サンプル評価セットの実行を試す

リポジトリのクローン

Evals のリポジトリは、扱うデータセットの大きさから Git LFS(Large File Storage)を使用しているようです。 私の環境には入っていなかったので、まずこちらから Git LFS をインストールしました。

# クローン
$ git clone https://github.com/openai/evals.git
$ cd ./evals

# Git LFSからコードを取得
$ git lfs fetch --all
$ git lfs pull

インストール

$ pip install -e .

実行

それではサンプルの評価用データセットを実行してみます。

oaievalというタスクランナーが用意されています。
実行には API Key を環境変数に設定する必要があります.

$ export OPENAI_API_KEY=<OPENAI APIのKEY>

test-match というサンプルのプロンプトの内容を見てみると

まず

「可能な限り簡潔にフレーズを埋めてください(Complete the phrase as concisely as possible.)」

というsystemロールからの命令が与えられ、3 つのテストケースが定義されていました。

# 入力 理想の回答
1 Once upon a time
2 The first US president was George Washington
3 OpenAI was founded in 20 15

生の json ファイルは以下です。

evals/evals/registry/data/test_match/samples.jsonl

{"input": [{"role": "system", "content": "Complete the phrase as concisely as possible."}, {"role": "user", "content": "Once upon a "}], "ideal": "time"}
{"input": [{"role": "system", "content": "Complete the phrase as concisely as possible."}, {"role": "user", "content": "The first US president was "}], "ideal": "George Washington"}
{"input": [{"role": "system", "content": "Complete the phrase as concisely as possible."}, {"role": "user", "content": "OpenAI was founded in 20"}], "ideal": "15"}

test-match を実行してモデルを評価します。

$ oaieval gpt-3.5-turbo test-match
[2023-03-15 16:49:56,833] [registry.py:145] Loading registry from /Users/~/evals/evals/registry/evals
[2023-03-15 16:49:56,846] [registry.py:145] Loading registry from /Users/~/.evals/evals
[2023-03-15 16:49:57,982] [oaieval.py:178] Run started: 230315074957JQFL4UL4
[2023-03-15 16:49:57,987] [eval.py:30] Evaluating 3 samples
[2023-03-15 16:49:58,012] [eval.py:136] Running in threaded mode with 10 threads!
100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 3/3 [00:01<00:00,  2.78it/s]
[2023-03-15 16:49:59,102] [record.py:320] Final report: {'accuracy': 1.0}. Logged to /tmp/evallogs/230315074957JQFL4UL4_gpt-3.5-turbo_test-match.jsonl
[2023-03-15 16:49:59,102] [oaieval.py:209] Final report:
[2023-03-15 16:49:59,102] [oaieval.py:211] accuracy: 1.0
[2023-03-15 16:49:59,113] [record.py:309] Logged 9 rows of events to /tmp/evallogs/230315074957JQFL4UL4_gpt-3.5-turbo_test-match.jsonl: insert_time=2.056ms

Accuracy が 1.0 で 3 ケースとも正答を返したことがわかります。

実行結果のレポートを見てみると。 ChatGPT が実際に返したレスポンスを見ることができます。

/tmp/evallogs/230315053524NR6IZ43D_gpt-3.5-turbo_test-match.json

{"spec": {"model_name": "gpt-3.5-turbo", "model_names": {"completions": ["gpt-3.5-turbo"]}, "eval_name": "test-match.s1.simple-v0", "base_eval": "test-match", "split": "s1", "run_config": {"model_specs": {"completions_": [{"name": "gpt-3.5-turbo", "model": "gpt-3.5-turbo", "is_chat": true, "encoding": null, "organization": null, "api_key": null, "extra_options": {}, "headers": {}, "strip_completion": true, "n_ctx": 4096, "format": null, "key": null, "group": null}], "embedding_": null, "ranking_": null}, "eval_spec": {"cls": "evals.elsuite.basic.match:Match", "args": {"samples_jsonl": "test_match/samples.jsonl"}, "key": "test-match.s1.simple-v0", "group": "test-basic"}, "seed": 20220722, "max_samples": null, "command": "/Users/kinjo.shuya/.local/share/virtualenvs/evals-gM99JQ0C/bin/oaieval gpt-3.5-turbo test-match", "initial_settings": {"visible": true}}, "created_by": "", "run_id": "230315053524NR6IZ43D", "created_at": "2023-03-15 05:35:24.407007"}}
{"final_report": {"accuracy": 1.0}}
{"run_id": "230315053524NR6IZ43D", "event_id": 0, "sample_id": "test-match.s1.2", "type": "raw_sample", "data": {"input": [{"role": "system", "content": "Complete the phrase as concisely as possible."}, {"role": "user", "content": "OpenAI was founded in 20"}], "ideal": "15"}, "created_by": "", "created_at": "2023-03-15 05:35:24.430998+00:00"}
{"run_id": "230315053524NR6IZ43D", "event_id": 1, "sample_id": "test-match.s1.1", "type": "raw_sample", "data": {"input": [{"role": "system", "content": "Complete the phrase as concisely as possible."}, {"role": "user", "content": "The first US president was "}], "ideal": "George Washington"}, "created_by": "", "created_at": "2023-03-15 05:35:24.431702+00:00"}
{"run_id": "230315053524NR6IZ43D", "event_id": 2, "sample_id": "test-match.s1.0", "type": "raw_sample", "data": {"input": [{"role": "system", "content": "Complete the phrase as concisely as possible."}, {"role": "user", "content": "Once upon a "}], "ideal": "time"}, "created_by": "", "created_at": "2023-03-15 05:35:24.432481+00:00"}
{"run_id": "230315053524NR6IZ43D", "event_id": 3, "sample_id": "test-match.s1.2", "type": "sampling", "data": {"prompt": [{"role": "system", "content": "Complete the phrase as concisely as possible."}, {"role": "user", "content": "OpenAI was founded in 20"}], "sampled": "15.", "options": ["15"], "picked": "15", "expected": ["15"], "match": true, "metadata": {"completion_id": "chatcmpl-6uE5Ec0lt05vpHzuYtivR7jLgAJFe", "model": "gpt-3.5-turbo-0301"}}, "created_by": "", "created_at": "2023-03-15 05:35:25.447669+00:00"}
{"run_id": "230315053524NR6IZ43D", "event_id": 4, "sample_id": "test-match.s1.2", "type": "match", "data": {"correct": true, "expected": "15", "picked": "15", "sampled": "15."}, "created_by": "", "created_at": "2023-03-15 05:35:25.447747+00:00"}
{"run_id": "230315053524NR6IZ43D", "event_id": 5, "sample_id": "test-match.s1.1", "type": "sampling", "data": {"prompt": [{"role": "system", "content": "Complete the phrase as concisely as possible."}, {"role": "user", "content": "The first US president was "}], "sampled": "George Washington.", "options": ["George Washington"], "picked": "George Washington", "expected": ["George Washington"], "match": true, "metadata": {"completion_id": "chatcmpl-6uE5FVxvGxiDVFa8cRhTMBZFYu5XM", "model": "gpt-3.5-turbo-0301"}}, "created_by": "", "created_at": "2023-03-15 05:35:25.579719+00:00"}
{"run_id": "230315053524NR6IZ43D", "event_id": 6, "sample_id": "test-match.s1.1", "type": "match", "data": {"correct": true, "expected": "George Washington", "picked": "George Washington", "sampled": "George Washington."}, "created_by": "", "created_at": "2023-03-15 05:35:25.579812+00:00"}
{"run_id": "230315053524NR6IZ43D", "event_id": 7, "sample_id": "test-match.s1.0", "type": "sampling", "data": {"prompt": [{"role": "system", "content": "Complete the phrase as concisely as possible."}, {"role": "user", "content": "Once upon a "}], "sampled": "time.", "options": ["time"], "picked": "time", "expected": ["time"], "match": true, "metadata": {"completion_id": "chatcmpl-6uE5FlxzUE2dMXuU7WrnchbotDa7N", "model": "gpt-3.5-turbo-0301"}}, "created_by": "", "created_at": "2023-03-15 05:35:25.604367+00:00"}
{"run_id": "230315053524NR6IZ43D", "event_id": 8, "sample_id": "test-match.s1.0", "type": "match", "data": {"correct": true, "expected": "time", "picked": "time", "sampled": "time."}, "created_by": "", "created_at": "2023-03-15 05:35:25.604410+00:00"}

カスタムの評価セットを試す

ChatGPT からの出力を評価するテンプレートと、データセットのサンプルを用いて、カスタムの eval(評価セット)を作ってみます。

データセットの用意

最初にsystemロールからの以下の命令を与えます。

あなたは日本語から沖縄の方言に翻訳する翻訳機械として振る舞ってください。ユーザーが〇〇はうちなーぐちで何?と聞いてくるので、うちなーぐちに翻訳して返答をしてください。

その後以下の 2 問の正答率を評価してみます。

# 入力 理想の回答
1 「ご馳走様」はうちなーぐちで何? くわっちーさびたん
2 「ありがとう」はうちなーぐちで何? にふぇーでーびる

実際の json ファイルは以下です。 このファイルをリポジトリのevals/registry/data/配下に eval の名前のディレクトリを作成し、json ファイルを格納します。

evals/registry/data/test_okinawan/samples.jsonl

{"input": [{"role": "system", "content": "あなたは日本語から沖縄の方言に翻訳する翻訳機械として振る舞ってください。ユーザーが〇〇はうちなーぐちで何?と聞いてくるので、うちなーぐちに翻訳して返答をしてください。"}, {"role": "user", "content": "「ご馳走様」はうちなーぐちで何?"}], "ideal": "くわっちーさびたん"}
{"input": [{"role": "system", "content": "あなたは日本語から沖縄の方言に翻訳する翻訳機械として振る舞ってください。ユーザーが〇〇はうちなーぐちで何?と聞いてくるので、うちなーぐちに翻訳して返答をしてください。"}, {"role": "user", "content": "「ありがとう」はうちなーぐちで何?"}], "ideal": "にふぇーでーびる"}

用意したデータセットを evals 配下のyamlファイルに登録する必要があります。 evals/registry/evals/test-basic.yamlに以下を追記しました。

evals/registry/evals/test-basic.yaml

test-okinawan:
  id: test-okinawan.dev.v0
  description: Example eval that checks sampled text matches the expected output.
  disclaimer: This is an example disclaimer.
  metrics: [accuracy]
test-okinawan.dev.v0:
  class: evals.elsuite.basic.match:Match
  args:
    samples_jsonl: test_okinawan/samples.jsonl

ここで指定するclassが、モデルからの応答の評価方法です。 評価方法には予めテンプレートが用意されています。テンプレートの範囲内での評価方法であれば、評価部分はノーコーディングで利用可能で、こちらですべき作業はプロンプトのデータセットを用意するのみです。

Match, Includes, FuzzyMatchといった基本的な評価テンプレートに加え、自由回答形式の質問の出力を評価するModelBasedClassifyと言うテンプレートも用意されていました。ModelBasedClassifyは、モデルの出力評価にさらにAIモデルを利用して評価するテンプレートで、回答のばらつきがあるプロンプトに対する出力を、より抽象的に評価するために利用できます。
もっと詳しく知りたい方は、eval-templates.mdをご確認ください。

評価結果

実行します

kinjo.shuya@HL00935 evals % oaieval gpt-3.5-turbo test-okinawan
[2023-03-15 18:47:49,452] [registry.py:145] Loading registry from /Users/~/evals/evals/registry/evals
[2023-03-15 18:47:49,466] [registry.py:145] Loading registry from /Users~/.evals/evals
[2023-03-15 18:47:50,524] [oaieval.py:178] Run started: 230315094750NWZLYXCQ
[2023-03-15 18:47:50,527] [data.py:78] Fetching test_okinawan/samples.jsonl
[2023-03-15 18:47:50,528] [eval.py:30] Evaluating 2 samples
[2023-03-15 18:47:50,550] [eval.py:136] Running in threaded mode with 10 threads!
100%|████████████████████████████████████| 2/2 [00:03<00:00,  1.71s/it]
[2023-03-15 18:47:53,977] [record.py:320] Final report: {'accuracy': 0.0}. Logged to /tmp/evallogs/230315094750NWZLYXCQ_gpt-3.5-turbo_test-okinawan.jsonl
[2023-03-15 18:47:53,977] [oaieval.py:209] Final report:
[2023-03-15 18:47:53,977] [oaieval.py:211] accuracy: 0.0
[2023-03-15 18:47:53,985] [record.py:309] Logged 6 rows of events to /tmp/evallogs/230315094750NWZLYXCQ_gpt-3.5-turbo_test-okinawan.jsonl: insert_time=7.172ms

精度は 0.0 、全問不正解でした・・ ログを確認すると確かに間違えています。

# 入力 理想の回答 実際の回答
1 「ご馳走様」はうちなーぐちで何? くわっちーさびたん 「いっとく(いとう)さーびる」と言います。
2 「ありがとう」はうちなーぐちで何? にふぇーでーびる 「なんくるないさー」が「ありがとう」に相当する沖縄の方言です。

私の知る限り「いっとくさーびる」と言う方言はなく、「なんくるないさー」もそこまで便利な言葉ではありません。
一部の地域の文化や言語など、学習データ外の情報については誤答率が高いという予想が当たりました。

プロンプトを変えて、精度が上がるかの検証をしていきたいところですが、今回はEvalsの検証記事なのでここまでとし、GPT-4 の API 利用が許可されたら同じデータで GPT-4 も評価してみたいと思います。

Evals はプロンプトエンジニアリングを加速するモデルの評価フレームワークでした

というわけで、今回は README.md に従い、サンプルの評価セットと、カスタムの評価セットを試してみました。

OpenAI の PlayGround でトライアンドエラーで行っていた作業が、eval を作成してコード化することで何度も実行できるようになる点に利点を感じています。 Evals を利用することで、プロンプトで与える情報量とモデルの精度の検証や、新たなモデルが公開された時に既存のプロンプトが新しいモデルでどのように動くのかを、再現性をもって検証することが可能になります。

GPT-3.5からGPT-4公開までのスピードを見ると、これまでライブラリのバージョンアップに追われていたように、AIモデルのバージョンアップ対応に追われる未来が予想できます。 GPTをアプリケーションに組み込む際に、アプリケーションで使用するプロンプトと期待結果をあらかじめ用意しておき、精度検証を自動化できるようにする必要があるなと感じました。

Evals 自体は、OSS でモデルの精度向上のための評価セットのコントリビュートを募集しています。期間限定で、品質の良い評価をコントリビュートした場合、GPT-4 のアクセス権がもらえるようです。興味のある方は、試してみてください。

以上。CX 事業本部 Delivery 部のきんじょーでした。