跳到正文
Hacker News· softwaredoug·· 7 天前AI 评分34

如何用 Qwen3-1.7B 构建自己的决策模型

Build your own decision model

AI 导读

用 Qwen/Qwen3-1.7B 通过约束解码把输出限制在 A-E 五个选项内,单次前向即可给出答案,在 CommonsenseQA 随机留出样本上准确率 725/1221(0.5938),微调后提升至 762/1221(0.6241)。但模型输出概率严重过度自信,0.9-1.0 置信区间准确率仅约 70%,作者用温度缩放拟合出 3.797 的温度值来校准。

正文 · AI 翻译

"System one" 决策模型是那些以校准概率或每个允许答案进行推断和响应的模型。

考虑你日常使用的语言模型,要从中获得类型化输出(JSON),你可以使用 Structured Output 将输出约束为有保证的有效 JSON。虽然模型在一次传递中预填充输入,但它仍然必须为每个 token 执行一次传递,以生成有效的响应。

在这个例子中,需要 11 次传递才能生成最终输出。(我们没有考虑推测解码和其他推理优化技术。)

提示(预填充) 已生成(预测) 正在预测

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

提示(预填充) 已生成(预测) 正在预测

由于输出被限制为仅一组固定选项,模型无法选择这些选项之外的任何内容。然而,这并不能保证输出是正确的。在这种情况下,通常也会将输出 token 概率视为置信度分数,但如果没有额外训练,这些分数很可能反映的是模型对下一个 token 的置信度,而不是响应为正确答案的真实概率。

构建你自己的

我们可以通过使用 LLM 约束输出 token 来模拟这种行为。这里我使用的是 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 仓库,其中的脚本会引导你构建数据集、评估、微调并校准你自己的模型。我鼓励你把它拉下来,并在其他更大的模型上试一试。

来源:Hacker News · nishtahir.com