VoxMem 的评测范围通常落在多轮音频问答上:把一段连续音频按轮次拆开,看模型在后面几轮里能不能正确复用前面听到的信息。要回答“能测哪些维度”,先去看评测配置里注册了哪些任务;要回答“记忆弱在哪”,不要只盯总分,而是把逐项结果导出,按任务类型和记忆维度分组再读。
从 VoxMem 拿到的不是一句“记忆好不好”,而是任务类型乘记忆维度的逐项得分。这条链路适合在多轮问答、信息检索、指代消解几类任务上做横向比较:先确认任务配置,再跑评测并导出明细,按记忆类型分组统计,画热力图定位弱点,最后只对拖后腿的维度补数据做微调。导出字段、任务名和命令名需要结合你本地的配置确认,不同实现版本可能不一致。
列出 VoxMem 支持的音频问答任务类型
任务类型一般写在评测配置或任务定义文件里,包含任务名、输入字段、评分方式。先把它们读出来,再按下面三类归并,否则后面看逐项结果时容易把不同性质的任务混在一起。
- 多轮问答:同一段音频里连续多轮提问,后面的问题依赖前文内容,评分看整条会话而非单轮。
- 信息检索:从音频里定位某段事实或某个片段,答案通常对应一个可枚举的证据区间。
- 指代消解:问题里出现“他”“它”“刚才那个”这类指代,需要回指到前面的实体或片段。
查看任务描述的通用骨架(子命令名以你本地 CLI 为准):
# 方式一:CLI 列出已注册任务
voxmem tasks list `--config` configs/audio_qa.yaml
# 方式二:直接读任务定义文件
python -c "import yaml,json;print(json.dumps(yaml.safe_load(open('configs/tasks.yaml')),ensure_ascii=False,indent=2))"
验证方式:输出里每个任务应当有名称、类型、输入字段、评分方式四项。如果任务定义没有显式类型字段,就看它的 prompt 模板和 judge 规则,人工归到上面三类,并把这个归类写进后续脚本的映射表里,不要临场再判断。
运行评测后导出逐项结果文件
总分是聚合结果,做归因要靠逐项明细。检查配置里是否有导出开关,没有就在评测命令上加导出参数。
# configs/audio_qa.yaml 中与导出相关的部分(字段名按本地实现替换)
eval:
export_items: true
export_path: results/eval_items.jsonl
export_format: jsonl # 可选 json / csv
voxmem eval run `--config` configs/audio_qa.yaml `--split` dev
# 若 CLI 支持直接指定导出目标,可写成:
# voxmem eval run `--config` configs/audio_qa.yaml `--split` dev \
# `--export` results/eval_items.jsonl `--export-format` jsonl
逐项文件里常见的字段含义:
item_id:样本标识,同一个会话的多个轮次共用前缀。task_type:任务类型,对应上一节归并出的三类。memory_scope:记忆维度,常见取值是单轮、多轮、跨段;若字段名叫memory_type或scope,脚本里换掉即可。turn_index:当前问题在会话中的轮次序号,用来观察“第几轮开始崩”。score:该项得分,通常是 0 到 1 的连续值或二值判定。pred/ref:模型输出与参考答案,用于人工回看失败样本。
验证方式:导出条数应当等于参与评测的样本数乘以对应轮次数(多轮任务按轮次展开)。用 wc -l results/eval_items.jsonl 和评测日志里的样本计数对一下,对不上就说明导出环节漏了某个任务集。
在结果文件里按记忆类型筛选指标
下面这段脚本按记忆类型分组统计,不依赖任何评测框架,只读逐项文件。三个口径建议这样定义:单轮记忆指答案只依赖当前轮;多轮记忆指依赖同一会话内前面若干轮;跨段记忆指依赖跨音频段或长间隔的前文。
import json
from collections import defaultdict
stat = defaultdict(list)
with open("results/eval_items.jsonl", encoding="utf-8") as f:
for line in f:
line = line.strip()
if not line:
continue
item = json.loads(line)
scope = item.get("memory_scope") or "unknown"
task = item.get("task_type") or "unknown"
stat[(scope, task)].append(item["score"])
for (scope, task), scores in sorted(stat.items()):
avg = sum(scores) / len(scores)
print(f"{scope:14s} {task:18s} n={len(scores):4d} avg={avg:.3f}")
拿到这张分组表后,重点看两件事:同一记忆维度在不同任务上的差距,以及同一任务在不同记忆维度上的差距。前者说明任务本身难不难,后者才更接近记忆机制的问题。分到 unknown 的项说明字段缺失或命名不一致,先回去修导出配置,不要带着 unknown 下结论。
用热力图展示模型在不同任务上的记忆强弱
分组表适合读数值,热力图适合一眼找弱点。把任务类型放横轴、记忆维度放纵轴,格子里的数字就是该组合的均分;颜色只是辅助,判断仍以数值为准。
import json
import numpy as np
import matplotlib.pyplot as plt
scopes = ["single_turn", "multi_turn", "cross_segment"]
tasks = ["multi_turn_qa", "retrieval", "coreference"]
acc = {}
with open("results/eval_items.jsonl", encoding="utf-8") as f:
for line in f:
item = json.loads(line)
acc.setdefault((item["memory_scope"], item["task_type"]), []).append(item["score"])
grid = np.full((len(scopes), len(tasks)), np.nan)
for i, s in enumerate(scopes):
for j, t in enumerate(tasks):
v = acc.get((s, t))
if v:
grid[i, j] = sum(v) / len(v)
fig, ax = plt.subplots(figsize=(6, 3.5))
im = ax.imshow(grid, cmap="RdYlGn", vmin=0.0, vmax=1.0)
ax.set_xticks(range(len(tasks)), tasks, rotation=20)
ax.set_yticks(range(len(scopes)), scopes)
for i in range(len(scopes)):
for j in range(len(tasks)):
if not np.isnan(grid[i, j]):
ax.text(j, i, f"{grid[i, j]:.2f}", ha="center", va="center")
fig.colorbar(im, ax=ax, label="score")
plt.tight_layout()
plt.savefig("results/memory_heatmap.png", dpi=150)
任务名和记忆维度名要和你配置里的字符串完全一致,否则格子会留空。留空的格子本身也是信息:说明该组合没有样本覆盖,先补评测集,再谈强弱。横轴任务较多时可以按任务族拆分多张图,避免一张图里格子过密难以阅读。
根据逐项结果决定是否微调模型
是否微调不取决于总分高低,而取决于失败是否集中在可训练的模式上。逐项结果里出现下面三个信号时,再考虑补数据微调。
- 任务类型整体低于基线。基线用上一版模型在同一 split、同一 prompt、同一评测配置下的导出结果,逐项字段对齐后再比较;换了 split 或改了 judge 规则就不具备可比性。
- 某个记忆维度拖后腿。例如跨段记忆的均分明显低于单轮记忆,或指代消解在多轮场景下分数掉得厉害,说明问题出在上下文组织而不是领域知识。
- 失败样本集中在特定轮次或特定指代链。按
turn_index再切一刀,看是不是前几轮正常、后面开始崩,或某类指代词反复出错。这种分布通常比整体分数更能说明训练数据缺什么。
对应的微调数据构造建议:
- 从失败项里按
task_type乘memory_scope分层抽样,不要只抓某一种任务,否则容易把其他维度的表现带下去。 - 每个训练样本保留完整的指代链上下文,缺前文的样本对跨段记忆没有帮助。
- 在答案相关轮次之间插入无关问答,让模型练习区分“该忘的”和“该留的”,这比单纯加长上下文更贴近多轮场景。
- 训练集与评测集不要同源;微调结束后用同一套导出脚本重跑,逐项字段对齐再比较,只看总分容易掩盖某些维度的退化。
如果三个信号都不明显,或失败样本分散在大量互不相关的任务上,通常先检查 prompt 模板、音频切分方式和评测字段定义,比直接微调更省成本。