登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  科技周边 >  人工智能

Transformers generate 怎样读取每条序列的分数

来源:17golang原创

时间:2026-11-01 03:21:44 249浏览 收藏

如果只调用 model.generate(),通常先拿到的是 token 序列;想知道每一步选中的 token 分数,不能只在返回值上猜字段,而要在调用时同时打开 return_dict_in_generate=True 和 output_scores=True。前者把结果变成带字段的输出对象,后者保留每个生成步的词表分数。随后用 compute_transition_scores() 把“选中的 token”与对应分数对齐。

官方文档:https://huggingface.co/docs/transformers/main_classes/text_generation

要点速览
  • return_dict_in_generate 决定是否返回包含可选字段的 ModelOutput,output_scores 决定是否保留生成阶段的分数。
  • outputs.scores 是按生成步组织的词表分数,不能直接当作每条序列的最终总分。
  • decoder-only 模型要从输入长度之后切出生成 token;beam search 还要把 beam_indices 交给对齐函数。

先让 generate 返回分数,而不是只返回 token 序列

这两个开关解决的是不同问题。return_dict_in_generate=True 让返回值拥有 sequences、scores 等命名字段;output_scores=True 才会要求生成过程保留各步预测分数。只开前者,可能得到一个有字段但没有逐步分数的结果;只关注后者,又无法稳定地按字段读取。

from transformers import AutoModelForCausalLM, AutoTokenizer

model_name = "openai-community/gpt2"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(model_name)

inputs = tokenizer("The quick brown", return_tensors="pt")
outputs = model.generate(
    **inputs,
    max_new_tokens=4,
    return_dict_in_generate=True,  # 让结果带有 sequences、scores 等字段。
    output_scores=True,             # 保留每个生成步的词表分数。
)

print(outputs.sequences.shape)
print(len(outputs.scores))  # 通常对应实际生成的步数。

outputs.sequences 保存最终 token 序列,outputs.scores 是一个按生成步排列的元组;每个元素通常包含当前批次中各候选 token 的分数。它们的用途不同:前者回答“生成了什么”,后者回答“每一步候选的模型分数是什么”。

Transformers generate 的输出开关、ModelOutput、sequences 和 scores 静态关系说明图
图1:结构说明图,展示 generate 的输出开关、序列和逐步分数之间的关系,不是运行截图。

用 transition scores 把分数对齐到生成 token

如果直接遍历 outputs.scores,你拿到的是每一步完整词表的分数,仍要知道最终序列在每一步选中了哪个 token。compute_transition_scores() 会根据序列、逐步分数和可选的 beam 路径,提取每个已选 token 对应的分数。

transition_scores = model.compute_transition_scores(
    outputs.sequences,
    outputs.scores,
    normalize_logits=True,  # 转成可比较的对数概率,便于解释。
)

# decoder-only 模型的序列包含输入提示词,所以从输入长度后开始对齐。
input_length = inputs.input_ids.shape[1]
generated_tokens = outputs.sequences[:, input_length:]

for token_id, score in zip(generated_tokens[0], transition_scores[0]):
    # 只打印生成部分,避免把提示词误当作模型新生成内容。
    token_text = tokenizer.decode(token_id)
    print(token_id.item(), token_text, float(score))

这里最容易错的是切片位置。对 decoder-only 模型,sequences 包含原始输入,所以 input_length 是输入 token 数;对 encoder-decoder 模型,文档示例通常把它视为解码端起始位置,不能机械套用 decoder-only 的切片。文章的重点是让 token 与分数使用同一段生成区间,而不是把输入提示词算进生成评分。

beam search 需要额外保留路径信息

贪心或采样场景通常只需要 sequences 和 scores。使用 beam search 时,一个最终序列可能在不同时间步来自不同 beam,单独按批次下标读取分数会丢失路径信息,因此要把 outputs.beam_indices 一起传给对齐函数。

beam_outputs = model.generate(
    **inputs,
    max_new_tokens=4,
    num_beams=3,
    num_return_sequences=3,
    return_dict_in_generate=True,  # 保留 beam 结果字段。
    output_scores=True,             # 保留每个 beam 的逐步分数。
)

beam_transition_scores = model.compute_transition_scores(
    beam_outputs.sequences,
    beam_outputs.scores,
    beam_outputs.beam_indices,      # 指明最终序列经过的 beam 路径。
    normalize_logits=False,         # 重建官方序列分数时保持原始口径。
)

print(beam_transition_scores.shape)
print(beam_outputs.sequences_scores)

transition_scores 是逐 token 的分数,而 sequences_scores 是 beam search 的序列级结果,二者不能当成同一个字段。官方文档还说明,若要从 transition scores 重建序列分数,归一化选项和长度惩罚必须与生成配置保持一致;展示单 token 置信度时可以选择归一化,但做序列分数复算时要先明确口径。

Transformers transition scores 将输入提示词、生成 token、beam 路径与序列分数对齐的关系说明图
图2:对齐说明图,展示 transition scores 如何连接生成 token、beam 路径和序列分数,不是推理结果截图。

兼容边界:不要把四种长度和分数混在一起

对象它回答的问题常见误读
sequences最终返回的 token 序列是什么把开头输入提示词也当作新生成内容
scores每个生成步对整个词表的预测分数把一个步的词表张量当成序列总分
transition_scores最终选中 token 在各生成步的分数忽略 beam 路径导致 token 与分数错位
sequences_scoresbeam search 的序列级评分直接与单 token 对数概率比较

落地时可以按这份清单复查:一是确认两个输出开关都打开;二是按模型类型确定输入长度;三是 beam search 传入 beam_indices;四是说明是否做 logits 归一化;五是把“逐 token 分数”和“序列级分数”分别保存。这样既能打印生成内容,也能为候选排序、调试解码策略留下可解释数据。

常见问题

为什么打开 output_scores 后还是没有 scores 字段?

先确认同时设置了 return_dict_in_generate=True,并检查实际调用使用的是同一个结果对象。只保留分数而不返回 ModelOutput 时,读取方式不会按本文字段组织。

normalize_logits 应该设为 True 还是 False?

如果想把每步分数解释为可比较的对数概率,通常选择 True;如果要按官方示例重建 beam search 的序列分数,应先保持与生成过程相同的未归一化口径,并配合长度惩罚理解结果。

为什么生成 token 数和输入长度容易算错?

decoder-only 的最终序列通常包含提示词,生成片段应从输入 token 数之后切出;encoder-decoder 的解码起点不同,应该依据模型类型和返回序列语义调整,不要复制同一条切片表达式。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>