문서

질문과 답

Laya의 질문은 타입이 지정된 의사결정이며, 타입이 무엇을 묻고 무엇을 돌려받는지를 모두 결정합니다. 세 가지가 있고, 하나의 상태가 단 한 번의 포워드 패스로 셋을 모두 담을 수 있습니다:

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

한 번의 포워드 패스가 셋 모두에 답합니다. 이것이 타입이 지정된 인터페이스의 요점입니다. noul 질문은 우연히 “yes”와 “no”인 선택지 두 개를 가진 choice가 아니라, 출력 형태가 다른 별개의 헤드이며, 타입이 Laya에게 어느 것을 쓸지 알려줍니다.

세 가지 타입

choice —— 집합에서 하나 고르기

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

criteria는 레이블에서 설명으로 가는 순서 있는 매핑입니다. 렌더링된 질문에서 순서는 위치 기반이므로, 같은 레이블을 다른 순서로 둔 두 질문은 서로 다른 질문입니다. 설명은 선택 사항이며, {"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 필드는 없습니다.

의사결정이 본래 예/아니오가 아닐 때는 두 선택지의 레이블을 바꿀 수 있습니다. labels는 정확히 false와 true 키를 받으며, 극성은 바뀌지 않습니다. noul은 여전히 P(true)입니다.

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

레이블은 모델이 틀리는 질문을 고치는 수단이 아닙니다. noul은 자기 선택지 레이블을 따르며, 영어 체크포인트에서 특히 그렇습니다. 따라서 의사결정처럼 읽히는 레이블 쌍(“approve” / “reject”)은 답을 상태가 아니라 레이블 쪽으로 끌어당길 수 있습니다. 레이블을 바꾸는 경우에는 의존하기 전에 자기 데이터로 검증하십시오.

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를 취하거나 legend에서 answer_confidence에 대응하는 값을 읽으십시오. score를 반올림해서 그것이 레이블이라고 가정하지 마십시오. legend는 어떤 인덱스가 무엇을 뜻하는지 추측하지 않도록 존재합니다.

신뢰도 읽기

모든 답은 두 개의 신뢰도 숫자를 가지며, 이들은 서로 다른 것을 측정합니다.

필드 무엇인가 이것으로 게이팅하는가?
answer_confidence max(p) — 보고되는 답의 확률 예, 적합 후에는
confidence 1 - H(p) / log(k) — 전체 분포가 얼마나 집중되어 있는지 아니오
probabilities 전체 분포 (choice, score) —

answer_confidence는 온도 스케일링이 적합하는 양이고 저장소의 모든 캘리브레이션 수치가 이것으로 계산되는 양이며, 그래서 이것으로 게이팅해야 합니다. 이것은 배포된 상태로는 캘리브레이션되어 있지 않습니다. 흔히 여기에 귀속되는 성질, 즉 신뢰도 c로 반환된 답 가운데 약 c가 맞는다는 성질은, 해당 체크포인트와 선택지 개수에 맞춘 홀드아웃 데이터로 온도를 적합하고 검증한 뒤에만 성립합니다. 배포되는 체크포인트는 과신하며 그 정도는 선택지 개수에 달려 있으므로, 조정되지 않은 임계값은 모델 자체의 정확도보다 낮게 선택할 수 있습니다(#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는 정규화 엔트로피입니다. 분포가 뾰족하면 높고 넓게 퍼져 있으면 낮으며, 최상위 답이 맞는지와는 무관합니다. 유용한 신호이지만 같은 척도가 아니므로, 둘을 하나의 숫자로 게이팅해서는 안 됩니다:

# 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에서는 구조상 둘이 같습니다. 선택지가 둘일 때 max(p, 1-p)는 max(p)이기 때문입니다. 따라서 noul 답만으로는 둘 중 어느 것을 읽고 있었는지 알 수 없습니다. 모든 타입에 두 키가 모두 있으므로 선택이 암시가 아니라 명시적입니다.

임계값은 정책이지 모델의 속성이 아닙니다. 두 체크포인트 모두 과신한 채로 배포되며, 얼마나 과신하는지는 선택지 개수에 달려 있으므로, 선택지 3개 질문에서 측정한 숫자는 선택지 20개 질문으로 이전되지 않습니다. 자기 데이터로 측정하십시오. BENCHMARKS.md의 캘리브레이션 절에 적합 루프와 적합된 값이 있습니다.

action과 act_probability

action.act_probability는 “에이전트가 이것에 대해 행동해야 하는가”에 대한 별개 헤드의 점수로, 답 자체의 신뢰도와 다릅니다. 모든 질문 타입에 대해 보고됩니다. 라이브러리 어디에서도 이것을 대신 임계값 처리해 주지 않습니다.

프리셋

흔한 경우에 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'가 발생합니다.

이 변환은 인라인할 수 있는 짧고 안정적인 매핑이며, 비공개 헬퍼에 손을 뻗지 않게 해 줍니다. 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 딕셔너리와 score 목록은 변경 없이 통과합니다.

이것을 전제로 설계하기 전에 알아 둘 한계

  • 선택지가 많을 때 신뢰도는 정답 보장이 아닙니다. 선택지 20개 질문에서는 맞는 답과 틀린 답의 분포가 크게 겹치며, 임계값이 모델 자체의 정확도보다 낮게 선택하게 될 수 있습니다. #394를 참고하십시오.
  • 부정은 강제 선택 질문에서 안정적으로 처리되지 않습니다. 취소 질문이 “취소하지 말라”고 말하는 상태에 대해 두 체크포인트 모두에서 높은 신뢰도로 취소 레이블을 돌려줄 수 있습니다. #377을 참고하십시오.
  • noul은 상태가 아니라 자기 레이블을 따를 수 있으므로, 레이블 변경은 검증하십시오.
  • 선택지가 많다고 공짜가 아닙니다. 대략 20개를 넘으면 모델이 빠르게 나빠집니다. 묻기 전에 큰 레이블 공간을 줄이려면 숏리스트 헬퍼를 쓰거나, 거친 질문 하나와 세밀한 질문 하나로 나누십시오.