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