Модели принятия решений 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 со скриптами, которые проводят через процесс создания датасета, оценки, дообучения и калибровки собственной модели. Рекомендуется клонировать репозиторий и попробовать на более крупных моделях.