Hacker News Best

Build your own decision model

6.9内容质量

TL;DR · AI 摘要

Build your own decision model By Nish Tahir — 10 Oct 2026 "System one" decision models are models that infer and respond...

核心要点

  • 主题聚焦:Build your own decision model
  • 来源:Hacker News Best,建议结合原文判断细节。
  • AI 分析暂不可用,本条为保底评分与摘要。
#AI#编程#开源
打开原文

构建自己的决策模型

By

Nish Tahir

—

2026年10月10日

"系统一"决策模型是通过校准概率或所有允许的答案进行推断和响应的模型。

以日常使用的语言模型为例,若要从模型中获取结构化输出(如JSON格式),可以使用结构化输出功能来约束输出为保证有效的JSON格式。虽然模型可以在一次前向传递中填充输入,但仍需对每个token进行多次传递才能生成有效响应。

在此示例中,生成最终输出需要11次传递。(我们未考虑推测解码和其他推理优化技术。)

kg-card-begin: html

步骤

播放

重置

提示(预填充)

生成(预测)

正在预测

kg-card-end: html

像Jev这样的决策模型假设存在固定选项供我们选择,并且可以通过单次传递快速完成选择。在此示例中,我们将可能的输出集约束为选项A、B、C、D、E。通过屏蔽词汇表中的其他项,模型只能输出这些token。通过选择概率最高的输出,我们就能得到答案。

由于输出被约束在固定选项集中,模型无法选择集外内容。但这也无法保证输出的正确性。在此场景中,输出token的概率通常被当作置信度分数处理,但未经额外训练时,这些分数更可能反映模型对下一个token的预测置信度,而非响应正确性的实际概率。

构建自己的模型

我们可以通过使用LLM约束输出token来模拟这种行为。这里我使用Qwen/Qwen3-1.7B

code
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()

# 加载分词器和模型
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(
    model_name,
    torch_dtype="auto",
    device_map="auto"
)

# 每个选项作为第一个助手token时模型会生成的token ID
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]
# 约束解码:仅允许选项token
probs = torch.softmax(logits[option_token_ids].float(), dim=-1)

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

使用简单问题进行测试时输出如下:

code
// 输入

{
    "question": "天空是什么颜色?",
    "A": "红色",
    "B": "蓝色",
    "C": "绿色",
    "D": "紫色",
    "E": "我不知道"
}

// 输出

prediction: B A: 0.0000 红色 B: 0.9988 蓝色 C: 0.0000 绿色 D: 0.0000 紫色 E: 0.0012 我不知道

code

模型能够正确理解输入并做出与正确答案合理对应的预测。

我们可以通过在公开数据集上运行模型来测试其准确性。我将其应用于 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

code

对于一个17亿参数的模型来说表现不错,对数据集进行快速微调后性能略有提升

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

code

# 模型校准

对模型进行非常模糊问题的测试展示了有趣的特性。

// 输入 { "question": "你最可能在哪里发现蝙蝠?", "A": "洞穴", "B": "棒球比赛", "C": "阁楼", "D": "动物园", "E": "体育用品店" }

// 输出

prediction: A A: 0.9978 洞穴 B: 0.0004 棒球比赛 C: 0.0017 阁楼 D: 0.0000 动物园 E: 0.0001 体育用品店

code

这里应该没有明确答案,但将输出概率视为伪"置信度"分数时,可以看出模型对这个答案的过度自信。

如果将我之前运行的评估中的置信度范围进行分箱,可以发现模型的置信度与其准确性不匹配。这意味着模型未经过校准。

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

code

我们可以注意到模型在0.9-1.0分箱中表现出极端的过度自信,但正确率仅为70%。当它以0.8-0.9的置信度进行预测时,准确率仅为40%左右。这表明模型在预测中普遍过度自信。

由于我们的目标是让模型输出能反映其准确性的分数,我们可以使用温度缩放来校准模型。通过调整温度值,我们可以平滑其输出概率分布曲线并将其缩放以近似其准确性。

温度 T

曲线拟合将温度参数拟合到模型的准确性上,我找到3.797280788421631作为温度值。

bin count 置信度 准确率 (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

code

这使我们获得了显著改善的校准效果。如果你想进一步探索,我在GitHub上创建了一个仓库,其中包含逐步引导你完成构建数据集、模型评估、微调和校准的完整脚本。我建议你克隆这个仓库,并尝试在其他更大的模型上进行验证。