ドキュメント

質問と答え

質問と答え

Laya の質問は型付きの意思決定で、その型が何を尋ね何が返るかの両方を決めます。3 種類あり、1 つの state が 1 回のフォワードパスでそのすべてを運べます。

import laya

agent = laya.load("convaiinnovations/laya")

questions = {
    "dept": {"type": "choice", "instructions": "Which team should handle this?",
             "criteria": {"billing": "money and invoices",
                          "technical": "bugs and outages",
                          "sales": "pricing and contracts"}},
    "urgent": {"type": "noul", "instructions": "Is this urgent?"},
    "severity": {"type": "score", "instructions": "How severe is this?",
                 "criteria": ["trivial", "minor", "moderate", "serious", "critical"]},
}

result = agent.system_one({"text": "I was charged twice and nobody has replied for a week. "
                                   "Please refund me."}, questions)
result["answers"]["dept"]["choice"]        # 'billing'
result["answers"]["urgent"]["noul"]        # 0.8727
result["answers"]["severity"]["score"]     # 2.9046

1 回のフォワードパスが 3 つすべてに答えます。これが型付きインターフェースの要点です。noul の質問は、たまたま「はい」「いいえ」である 2 つの選択肢を持つ choice ではありません —— 別の出力形状を持つ別のヘッドであり、型が Laya にどちらを使うかを伝えます。

3 つの型

choice — 集合から 1 つ選ぶ

{"type": "choice",
 "instructions": "Which team should handle this?",
 "criteria": {"billing": "money and invoices", "technical": "bugs and outages"}}

criteria はラベルから説明への順序付きマッピングです。順序は位置的で、レンダリングされた質問において意味を持ちます。だから同じラベルを違う順序で並べた 2 つの質問は別の質問です。説明は任意で、{"billing": None} はラベルだけをレンダリングします。ラベルは書いたとおりに返るので、文字列でないラベルは choice ではそれ自身として、probabilities ではキーとして返ります。

説明は書く価値があります。装飾ではありません。レンダリングされた質問テキストがモデルの読むものなので、素の {"a": None, "b": None} では選択肢を区別する材料を何も与えません。

{"type": "choice", "choice": "billing",
 "probabilities": {"billing": 0.9881, "technical": 0.0057, "sales": 0.0062},
 "confidence": 0.9339, "answer_confidence": 0.9881,
 "action": {"act_probability": 1.0}}

noul — はい / いいえ

{"type": "noul", "instructions": "Is this urgent?"}

noul は二者択一の意思決定に対するこのプロジェクトの呼び名で、その答えはしきい値化された真偽値ではなく真である確率です。

{"type": "noul", "noul": 0.8727, "confidence": 0.8727, "answer_confidence": 0.8727,
 "action": {"act_probability": 1.0}}

しきい値は自分で選ぶものです。偽陽性がいくらかかるかによるからです。意思決定と取り違える bool フィールドはありません。

意思決定が自然にははい/いいえでないときは、2 つの選択肢にラベルを付け替えられます。labels は false と true というキーをちょうど取り、極性は変わりません。noul は依然として P(true) です。

{"type": "noul", "instructions": "Does this need a human?",
 "labels": {"false": "automatic", "true": "escalate"}}

ラベルは、モデルが間違える質問を直す手段ではありません。noul は自分の選択肢ラベルに従い、英語チェックポイントでは特に強く従うので、意思決定として読めるラベルの組(「approve」/「reject」)は答えを state ではなくラベルのほうへ引っ張りえます。ラベルを付け替えたら、それに頼る前に自分のデータで検証してください。

score — 順序付きの水準

{"type": "score", "instructions": "How severe is this?",
 "criteria": ["trivial", "minor", "moderate", "serious", "critical"]}

criteria は順序付きリストで、昇順でなければなりません。位置がそのまま尺度です。

{"type": "score", "score": 2.9046,
 "legend": {"0": "trivial", "1": "minor", "2": "moderate", "3": "serious", "4": "critical"},
 "probabilities": {"0": 0.0134, "1": 0.05, "2": 0.0518, "3": 0.7881, "4": 0.0967},
 "confidence": 0.5187, "answer_confidence": 0.7881,
 "action": {"act_probability": 1.0}}

score は期待値であり、最も確からしい水準ではありません。 上では score は 2.90 ですが、単一で最も確からしい水準は 0.788 の serious(3)です。どちらも有用で、別の問いに答えます。期待値は尺度上の二乗誤差を最小化し、argmax はモデルとの不一致を最小化します。ラベルが欲しいなら probabilities の argmax を取るか、answer_confidence の対応物を legend から読んでください。score を丸めてそれをラベルとみなしてはいけません。legend はどのインデックスが何を意味するかを推測せずに済むように存在します。

信頼度を読む

すべての答えは 2 つの信頼度の数値を運び、それらは別々のものを測ります。

フィールド それが何か これでゲートするか
answer_confidence max(p) —— 報告される答えの確率 はい、当てはめの後で
confidence 1 - H(p) / log(k) —— 分布全体がどれだけ集中しているか いいえ
probabilities 完全な分布(choice、score) —

answer_confidence は temperature スケーリングが当てはめる量であり、リポジトリのあらゆるキャリブレーションの数値が計算される量でもあり、それがこれをゲートすべき唯一のものにしています。ただし出荷時のままではキャリブレーションされていません。通常これに帰せられる性質 —— 信頼度 c で返された答えのうち約 c が正しい —— は、temperature を当てはめ、自分のチェックポイント・自分の選択肢数に対する未見データで検証して初めて成り立ちます。出荷時のチェックポイントは過信で、その度合いは選択肢数に依存するので、調整していないしきい値はモデル自身の accuracy を下回るものを選びえます(#394)。

# THRESHOLD is a number you measured on your own held-out data, not one the model ships.
# Fit and validate the temperatures first — the fine-tuning notebook has the loop:
#   notebooks/laya_finetune_typed_decisions_2xT4_kaggle.ipynb
ans = result["answers"]["dept"]
if ans["answer_confidence"] >= THRESHOLD:
    ...

confidence は正規化エントロピーです。分布が尖っていれば高く、広がっていれば低く、一番上の答えが正しいかどうかとは無関係です。有用なシグナルですが同じ尺度ではないので、2 つを 1 つの数値に対してゲートしてはいけません。

# the same three answers, and the two numbers are not the same
dept      confidence 0.9339   answer_confidence 0.9881
urgent    confidence 0.8727   answer_confidence 0.8727
severity  confidence 0.5187   answer_confidence 0.7881

noul では構成上等しくなります(2 つの選択肢の上では max(p, 1-p) は max(p))。なので noul の答えでは、どちらの数値を読んでいるのか分かりません。どちらのキーもすべての型に存在するので、選択は暗黙ではなく明示的です。

しきい値はポリシーであり、モデルの性質ではありません。 どちらのチェックポイントも過信で、その度合いは選択肢数に依存するので、3 択の質問で測った数値が 20 択の質問に移ることはありません。自分のデータで測ってください。BENCHMARKS.md のキャリブレーションの節に、当てはめのループと当てはめた値があります。

action と act_probability

action.act_probability は「エージェントがこれにそもそも行動すべきか」に対する別のヘッドのスコアで、答え自身の信頼度とは別です。すべての質問の型で報告されます。ライブラリのどこもこれを自動でしきい値化しません。

プリセット

3 つの既製の質問セットがあり、よくあるケースで criteria を手書きせずに済みます。

from laya import triage_questions, guard_questions, moderation_questions

agent.system_one(ticket, triage_questions())

契約ではなく出発点として使ってください。生成される質問を render_options で読み、出荷する前にラベルが自分のドメインに合うか確かめてください。

選択肢を読み戻す

選択肢の順序は位置的で、モデルが読むのは選択肢のテキストなので、何が送られたかを正確に見られる価値があります。

from laya import render_options

render_options({"t": "choice", "crit": {"billing": None, "sales": "pricing"}})
# ['billing', 'sales: pricing']

render_options({"t": "score", "crit": ["low", "high"]})
# ['level 0: low', 'level 1: high']

同じラベルでも違う順序ならその順序でレンダリングされます。だから順序は質問の同一性の一部なのです。

render_options({"t": "choice", "crit": {"x": "first", "y": "second"}})
# ['x: first', 'y: second']
render_options({"t": "choice", "crit": {"y": "second", "x": "first"}})
# ['y: second', 'x: first']

キーに注意してください。 render_options は、質問に書く {"type": ..., "criteria": ...} の形ではなく、内部の短キーの形 {"t": ..., "crit": ...} を取ります。公開の形を渡すと KeyError: 't' が上がります。

変換は短く安定したマッピングで、インライン化できます。これにより private なヘルパーに手を伸ばさずに済みます —— Agent._to_internal は内部であり、変わりえます。

def as_internal(q):
    """The short-key shape `render_options` reads, from a question as you wrote it."""
    crit = q.get("criteria")
    if q["type"] == "choice" and isinstance(crit, list):
        crit = {c: None for c in crit}
    return {"t": q["type"], "ins": q["instructions"], "crit": crit}

render_options(as_internal(question))

これは、ラベルのリストとして書かれた choice の質問に対してライブラリがすることと同じです。criteria の dict と score のリストはそのまま通ります。

これを踏まえて設計する前に知っておくべき制限

  • 選択肢数が多いとき、信頼度は正しさの保証ではありません。 20 択の質問では、正解と不正解の分布が大きく重なり、しきい値がモデル自身の accuracy を下回るものを選びえてしまいます。#394 を参照してください。
  • 強制選択の質問では否定が確実には扱われません。 キャンセルしないと言っている state に対して、両方のチェックポイントで高信頼度のままキャンセルのラベルを返すことがあります。#377 を参照してください。
  • noul は state ではなくラベルに従うことがあるので、ラベルを付け替えたら検証してください。
  • 選択肢が多いのは無料ではありません。 およそ 20 を超えるとモデルは急速に劣化します。大きなラベル空間は尋ねる前に短縮ヘルパーで減らすか、粗い質問と細かい質問に分けてください。