多模态模型评测怎样分别记录图像理解与文本生成指标为什么结果为空或不符合预期
来源:17golang原创
时间:2026-11-10 02:26:37 383浏览 收藏
我在整理多模态评测报表时遇到过一个很容易误判的现象:图像理解样本和文本生成样本都进入了同一个列表,评测脚本却只留下一个空结果,或者图像指标看起来正常、文本指标被覆盖。根因通常不是“模型没有输出”,而是把两种任务当成了同一种指标输入。
直接做法:保留统一的样本记录,但按 task 分成图像理解和文本生成两条评测链;每条链分别准备匹配的 predictions、references 和指标,最后用 vision、text 两个命名空间合并结果。这样,空结果可以继续从数据、指标契约和分布式进程三个层面定位。
官方地址:https://huggingface.co/docs/evaluate/
本文示例使用 Hugging Face Evaluate 的通用接口说明记录方式。示例没有声称某个模型已经取得特定分数,图中的结构也是原创说明图,不是运行截图。
先把两类任务拆成独立评测边界
图像理解可以是分类、图像问答或 OCR,文本生成则可能是摘要、翻译或开放式回答。它们都叫“多模态评测”,却不代表可以共用同一组参考值。分类通常是标签对标签,生成任务则是文本对文本;如果把它们混在一个 predictions 数组里,指标不知道该如何解释每个元素。

我会让每条记录至少包含样本 ID、任务类型、预测值和参考值,指标不直接写死在数据对象里,而是通过任务类型选择。这样做的关键不是字段多,而是让“什么输出对应什么答案”在数据层面可见。
from dataclasses import dataclass
from typing import Any
@dataclass
class EvaluationRecord:
# sample_id 用来把预测、参考答案和最终结果串起来
sample_id: str
# task 只允许使用评测流水线已经支持的任务名
task: str
# prediction 可以是标签,也可以是生成文本
prediction: Any
# reference 必须和 prediction 属于同一种可比较结构
reference: Any
records = [
EvaluationRecord("img-001", "image_understanding", "cat", "cat"),
EvaluationRecord("txt-001", "text_generation", "短摘要", "参考摘要"),
]
def split_records(items: list[EvaluationRecord]) -> dict[str, list[EvaluationRecord]]:
# 分组时保留原记录,不把两种任务强行转换成同一种值
groups = {"image_understanding": [], "text_generation": []}
for item in items:
if item.task not in groups:
raise ValueError(f"不支持的评测任务: {item.task}")
groups[item.task].append(item)
return groups
这里的分组只是数据边界,不代表图像任务必须使用图像专属指标,也不代表文本生成只能使用一种指标。真正的选择要看数据集的答案形式和指标卡要求。
图像理解先确定答案是标签还是开放文本
如果图像理解任务输出的是固定标签,例如 cat、dog,可以把它作为分类评测,使用 accuracy 这一类标签指标。如果输出的是“图中有什么”这样的自由文本,先要约定大小写、空格、标点和同义表达是否归一化,再决定使用 exact match、基于重叠的文本指标,还是专门的 VQA 评测器。
Evaluate 的通用调用要求把预测值和参考值明确传给 compute()。不要把原始图片路径、提示词、模型响应元数据误当成参考答案一起传入;这些信息可以留在记录里,但不是当前指标的比较对象。
import evaluate
def score_image_understanding(items: list[EvaluationRecord]) -> dict[str, float]:
# 这个示例只处理已经归一化的单标签图像理解结果
predictions = [item.prediction for item in items]
references = [item.reference for item in items]
if not predictions or len(predictions) != len(references):
raise ValueError("图像理解预测与参考答案数量不一致")
accuracy = evaluate.load("accuracy")
# predictions 和 references 的每个位置代表同一个样本
return accuracy.compute(predictions=predictions, references=references)
如果你的图像任务是开放式问答,不要为了复用上面的代码把长答案强行映射成一个类别。可以在进入指标前增加一个明确的规范化函数,并把原始回答和规范化回答同时保留,方便后续解释“分数低”究竟来自事实错误还是表达差异。
文本生成单独准备可对齐的参考文本
文本生成指标更依赖参考文本的结构。摘要、翻译和对话回答都可能有多种合理写法,因此 sample_id 要先对齐,不能依赖两个列表“刚好同序”。对于一个样本有多个参考答案的情况,数据结构也要按目标指标支持的形状准备,而不是在函数内部临时拼接字符串。
import evaluate
def score_text_generation(items: list[EvaluationRecord]) -> dict[str, float]:
# 文本生成只接收文本任务,避免把图像标签送进 ROUGE
if any(item.task != "text_generation" for item in items):
raise ValueError("文本指标收到非文本生成样本")
predictions = [str(item.prediction).strip() for item in items]
references = [str(item.reference).strip() for item in items]
if not predictions or len(predictions) != len(references):
raise ValueError("文本生成预测与参考文本数量不一致")
rouge = evaluate.load("rouge")
# ROUGE 需要每个预测文本与对应参考文本保持相同样本位置
return rouge.compute(predictions=predictions, references=references)
这类文本指标可能需要额外的分词、句法或模型资源。依赖没有准备好时应当让任务明确失败并保留错误信息,不要把异常吞掉后写入一个空字典,否则报表看起来像“模型没有分数”。
统一保存结果,但不要覆盖不同任务的指标
评测结果建议按任务命名空间保存。不要直接使用 results.update() 把两个字典合并到同一层,尤其是两套指标可能出现同名键时。结果记录里同时保存样本数、指标名和计算状态,后续看报表时才能知道空值是“没有样本”还是“非主进程没有返回值”。
def build_report(records: list[EvaluationRecord]) -> dict[str, object]:
# 先按任务拆分,再让每个指标函数独立处理自己的输入契约
groups = split_records(records)
vision_items = groups["image_understanding"]
text_items = groups["text_generation"]
report: dict[str, object] = {
"vision": {"sample_count": len(vision_items), "metrics": {}},
"text": {"sample_count": len(text_items), "metrics": {}},
}
if vision_items:
report["vision"]["metrics"] = score_image_understanding(vision_items)
if text_items:
report["text"]["metrics"] = score_text_generation(text_items)
return report
在实际项目中,类型检查器可能会提示嵌套字典的类型不够具体。可以把报告定义成 TypedDict 或单独的数据类;这里重点是结果结构,不是让一段示例代码覆盖所有工程类型。
为什么结果会为空
我排查这类问题时会先看“结果在哪一层变空”,而不是马上替换指标。Evaluate 的 compute() 会收集预测和参考值后计算最终结果;在分布式评测里,非主进程可能返回 None,这并不等于模型没有预测。只有主进程的返回值才适合作为最终报表来源。

可以按下面四种情况逐层判断:
| 现象 | 优先检查 | 处理方式 |
|---|---|---|
| 结果是 None | 是否运行在分布式非主进程 | 只在主进程汇总或写入最终报表 |
| 结果是空字典 | 输入样本是否为空、指标是否被条件分支跳过 | 记录 sample_count,并让空批次显式失败 |
| 指标报错或字段不匹配 | predictions/references 的元素形状 | 按指标卡要求整理字段,不混入图片元数据 |
| 分数异常低或全为零 | 样本 ID、文本规范化和参考答案对齐 | 打印少量脱敏前后样本,核对同一位置是否对应同一题 |
特别容易漏掉的是分布式边界:如果每个进程都把自己的返回值写到同一个文件,后写入的空值可能覆盖主进程结果。因此,文件写入也应当有主进程门禁,或者先收集各进程的样本,再由统一汇总点写报表。
把门禁放在指标调用之前
门禁的目的不是增加一套“验证系统”,而是让错误停在最接近根因的位置。对图像理解,至少检查任务名和标签值;对文本生成,至少检查文本非空、样本 ID 不重复、预测与参考数量一致。检查失败时带上任务名和样本 ID,后续通知才有可操作性。
def check_inputs(items: list[EvaluationRecord], task: str) -> None:
# 先确认分支没有混入另一类任务
if any(item.task != task for item in items):
raise ValueError(f"{task} 批次混入了其它任务")
if not items:
raise ValueError(f"{task} 没有可评测样本")
ids = [item.sample_id for item in items]
if len(ids) != len(set(ids)):
raise ValueError(f"{task} 存在重复 sample_id")
if any(item.prediction is None or item.reference is None for item in items):
raise ValueError(f"{task} 存在缺少 prediction 或 reference 的样本")
如果业务允许空批次,例如某个数据切片本来就没有图像题,那么不要把它伪装成成功的空分数。可以将状态记录为 skipped,并带上原因;这样看趋势时不会把“没有样本”误读成“模型得分为零”。
失败处理和通知应该记录什么
我倾向于把一次评测拆成四个可通知状态:prepared 表示样本已对齐,scored 表示指标返回字典,skipped 表示该任务没有样本,failed 表示输入契约、依赖或进程汇总失败。状态与分数分开保存,避免用空值同时表示四种不同含义。
def metric_status(result: object, sample_count: int) -> str:
# None 通常需要结合分布式进程信息判断,不能直接当作零分
if sample_count == 0:
return "skipped"
if result is None:
return "failed"
if not isinstance(result, dict) or not result:
return "failed"
return "scored"
通知消息至少带上任务名、样本数、指标名、进程角色和错误摘要。不要在通知里塞完整模型输出或敏感输入;评测流水线只需要让维护者知道该去哪个样本分片、哪个指标和哪个进程看原因。
一张表确认这套拆分是否适合你
| 问题 | 图像理解 | 文本生成 |
|---|---|---|
| 预测形态 | 标签或规范化答案 | 字符串,可能对应多个参考文本 |
| 常见指标 | accuracy、exact match 或任务专用指标 | ROUGE、BLEU 或任务专用指标 |
| 最容易漏的字段 | 答案规范化规则 | sample_id 与 reference 对齐关系 |
| 空结果优先排查 | 是否根本没有图像任务样本 | 是否被条件分支跳过或依赖未安装 |
如果一个模型同时处理图像和文本,模型推理层可以共享;评测层不必共享。把共享点放在“样本 ID、原始输入和模型版本”这些元数据上,把比较逻辑留在各自指标边界里,通常更容易扩展。
总结
多模态模型评测结果为空时,先不要把指标换一遍。先按任务拆分记录,保证每一条 prediction 与 reference 真正可比;再把图像理解和文本生成的分数放到不同命名空间。最后检查空批次、字段形状、指标依赖和分布式主进程返回值。
这套方式适合已经有一条推理流水线、但评测报表开始混乱的团队。如果你需要比较开放式视觉问答的事实正确性,仍应引入任务专用数据集和人工或模型辅助评审;不要把一个分类 accuracy 当成所有多模态能力的总分。
相关问题
为什么 predictions 和 references 数量一样,分数仍然异常?数量相同只说明列表长度一致,还要核对 sample_id、答案规范化和每个位置是否对应同一条样本。
分布式评测里每个进程都能拿到结果吗?不能假设这一点。非主进程的返回值可能是 None,最终报表应由主进程或统一汇总点负责。
图像问答能直接使用文本生成指标吗?只有当答案形态、参考文本和指标假设相符时才可以;开放式视觉问答通常需要先明确规范化、多个参考答案和人工质量边界。
-
Golang · Go教程 | 5个月前 | 超时控制 · 故障排查 · Go教程 · 后端工程 · Golang实战 · HTTP客户端 · golang Go 性能优化 net/http context Transport 超时 http.Client 生产实践205 收藏
-
Golang · Go教程 | 3个月前 | 并发 · HTTP · 性能优化 · 故障排查 · Go教程 · Go Goroutine 连接复用 pprof http.Client close Response.Body201 收藏
-
Golang · Go教程 | 3个月前 | golang · JSON · 故障排查 · Go教程 · 接口设计 · JSON Go 接口兼容性 DisallowUnknownFields 严格解码174 收藏
-
423 收藏
-
Golang · Go教程 | 2个月前 | golang · 服务端 · 故障排查 · net/http · 连接泄漏 StateIdle ConnState Go net/http StateHijacked311 收藏
-
139 收藏
-
242 收藏
-
374 收藏
-
442 收藏
-
289 收藏
-
158 收藏
-
400 收藏
-
500 收藏
-
102 收藏
-
145 收藏
-
212 收藏
-
科技周边 · 人工智能 | 2天前 | 人工智能 · vLLM KV Cache gpu-memory-utilization max-num-batched-tokens max-num-seqs 批处理显存265 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习