Модели принятия решений System 1

System One модели выводят и возвращают калиброванные вероятности для каждого допустимого ответа.

Обычная языковая модель, выдающая структурированный вывод в формате JSON через Structured Output, вынуждена выполнять проход для каждого токена, чтобы гарантировать валидный JSON. Несмотря на то что модель заполняет входные данные за один раз, ей всё равно требуется ещё один проход на каждый генерируемый токен.

В приведённом примере требуется 11 проходов для создания финального вывода. (Здесь не учитываются спекулятивное декодирование и другие техники оптимизации вывода.)

Модели выбора, такие как Jev, исходят из предположения, что существует фиксированный набор вариантов для выбора, доступных за один проход. В этом примере возможные выходные данные ограничены вариантами A, B, C, D, E. Маскируя остальные элементы в словаре, модель может генерировать только эти токены. Выбрав вариант с наибольшей вероятностью, получаем ответ.

Поскольку выходные данные ограничены только фиксированным набором вариантов, модель не может выбрать ничего за пределами этого набора. Однако это не гарантирует, что ответ будет правильным. Обычно вероятности выходных токенов трактуют как оценку уверенности в этом контексте, но без дополнительного обучения эти оценки, скорее всего, отражают уверенность модели в том, какой будет следующий токен, а не истинную вероятность того, что ответ правильный.

Создаём собственную реализацию

Можно воспроизвести это поведение, ограничивая выходные токены языковой моделью. Здесь используется Qwen/Qwen3-1.7B:

import argparse
import json

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer

model_name = "Qwen/Qwen3-1.7B"
options = ["A", "B", "C", "D", "E"]

parser = argparse.ArgumentParser()
parser.add_argument("--input", default="question.json")
args = parser.parse_args()

# load the tokenizer and the model
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(
    model_name,
    torch_dtype="auto",
    device_map="auto"
)

# the token the model would emit for each option as the first assistant token
option_token_ids = [tokenizer.encode(opt, add_special_tokens=False)[0] for opt in options]


def format_prompt(item):
    prompt = item["question"] + "\n"
    for opt in options:
        prompt += f"{opt}. {item[opt]}\n"
    prompt += "Answer:"
    messages = [
        {"role": "user", "content": prompt}
    ]
    return tokenizer.apply_chat_template(
        messages,
        tokenize=False,
        add_generation_prompt=True,
        enable_thinking=False
    )


with open(args.input) as f:
    item = json.load(f)

model_inputs = tokenizer(format_prompt(item), return_tensors="pt").to(model.device)
with torch.no_grad():
    logits = model(**model_inputs).logits[0, -1]
# constrained decoding: only the option tokens are allowed
probs = torch.softmax(logits[option_token_ids].float(), dim=-1)

print(f"prediction: {options[probs.argmax().item()]}")
for opt, prob in zip(options, probs.tolist()):
    print(f"{opt}: {prob:.4f}  {item[opt]}")

Запуск на простом тестовом вопросе даёт следующий результат:

// input

{
    "question": "What color is the sky?",
    "A": "Red",
    "B": "Blue",
    "C": "Green",
    "D": "Purple",
    "E": "I don't know"
}

// output

prediction: B
A: 0.0000  Red
B: 0.9988  Blue
C: 0.0000  Green
D: 0.0000  Purple
E: 0.0012  I don't know

Модель сумела разобраться в входных данных и дала предсказание, которое разумно соответствует правильному ответу.

Точность модели можно проверить на открытых датасетах. Было проведено тестирование на случайной выборке из CommonsenseQA:

    precision    recall        f1   support
A    0.5733    0.7197    0.6382       239
B    0.5506    0.7686    0.6416       255
C    0.5372    0.6598    0.5922       241
D    0.7206    0.3904    0.5065       251
E    0.7519    0.4255    0.5435       235
accuracy: 725/1221 = 0.5938
macro f1: 0.5844

Неплохой результат для модели размером 1,7B параметров. Быстрая дообучение на датасете показало немного лучшие показатели:

    precision    recall        f1   support
A    0.6475    0.6611    0.6542       239
B    0.6113    0.6784    0.6431       255
C    0.6234    0.5975    0.6102       241
D    0.6700    0.5418    0.5991       251
E    0.5808    0.6426    0.6101       235
accuracy: 762/1221 = 0.6241
macro f1: 0.6234

Калибровка модели

Тестирование на неоднозначной проблеме выявляет интересный момент:

// input
{
    "question": "Where would you most likely find a bat?",
    "A": "Cave",
    "B": "Baseball game",
    "C": "Attic",
    "D": "Zoo",
    "E": "Sporting goods store"
}

// output

prediction: A
A: 0.9978  Cave
B: 0.0004  Baseball game
C: 0.0017  Attic
D: 0.0000  Zoo
E: 0.0001  Sporting goods store

Здесь не должно быть очевидного ответа, но если трактовать выходные вероятности как псевдо-оценку уверенности, видно, что модель чрезмерно уверена в этом ответе.

Если разбить диапазоны оценок уверенности на интервалы, можно увидеть, что уверенность модели не соответствует её точности. Это означает, что модель не откалибрована:

         bin   count  confidence  accuracy
(0.00, 0.10]       0      0.0000    0.0000
(0.10, 0.20]       0      0.0000    0.0000
(0.20, 0.30]       3      0.2834    0.0000
(0.30, 0.40]      26      0.3761    0.2692
(0.40, 0.50]      41      0.4538    0.2683
(0.50, 0.60]      70      0.5490    0.3286
(0.60, 0.70]      74      0.6476    0.3649
(0.70, 0.80]      77      0.7495    0.4286
(0.80, 0.90]     121      0.8555    0.4711
(0.90, 1.00]     809      0.9855    0.7009

Видно, что модель крайне переоценивает уверенность в диапазоне 0,9–1,0, но она правильна лишь в 70% случаев. Когда модель делает предсказание с уверенностью 0,8–0,9, её точность составляет примерно 40%. Это означает, что модель в целом чрезмерно уверена в своих предсказаниях.

Если цель — получить оценки, отражающие реальную точность, один из методов калибровки — использование температурного масштабирования. Изменяя параметр температуры, можно сгладить кривую распределения вероятности выхода и масштабировать её для соответствия точности модели.

При подборе параметра температуры к точности модели методом кривой подгонки получился коэффициент 3.797280788421631:

         bin   count  confidence  accuracy
(0.00, 0.10]       0      0.0000    0.0000
(0.10, 0.20]       0      0.0000    0.0000
(0.20, 0.30]      82      0.2712    0.2317
(0.30, 0.40]     217      0.3507    0.3917
(0.40, 0.50]     199      0.4472    0.5126
(0.50, 0.60]     166      0.5475    0.5482
(0.60, 0.70]     139      0.6562    0.5827
(0.70, 0.80]     140      0.7492    0.7714
(0.80, 0.90]     169      0.8507    0.7988
(0.90, 1.00]     109      0.9333    0.9541

Это обеспечивает гораздо лучшую калибровку. Для экспериментов создан репозиторий на GitHub со скриптами, которые проводят через процесс создания датасета, оценки, дообучения и калибровки собственной модели. Рекомендуется клонировать репозиторий и попробовать на более крупных моделях.