转写出来的说话人标签怎么对应到真人?转写错字怎么人工修订?
简化版
说话人分离只回答「哪几句是同一个人说的」,给出的是 0、1、2 这样的编号,不回答「这个人是谁」。把编号对到真人有三条路:人工指认、声纹注册后比对、按发言内容推断(点名、自我介绍、职务相关的话题)。不管走哪条路,写库都应该按标签整批写,一个标签选一次人;候选范围收窄到这场会议的参会人员;机器给出的只是建议,最后由人确认。
转写错字的修订要保留模型原文:原文一份不动,另存一份可修订的文本,并记下谁在什么时候改的。下游的全文、纪要、检索统一读修订后的文本,改了之后由它派生的数据要跟着刷新;原文留作对照,也能用来统计转写质量。
详细版
| 办法 | 前提 | 优点 | 局限 |
|---|---|---|---|
| 人工指认 | 有人愿意听几句、选个人 | 最准,不需要额外数据 | 标签多、会议多时费人工 |
| 声纹比对 | 事先采集每个人的声纹 | 全自动,跨会议稳定 | 要注册声纹,涉及生物特征隐私;换设备、感冒会漂移 |
| 内容推断 | 发言里有点名、自我介绍 | 不需要声纹,能给出依据 | 依赖会议里有线索,只能作为建议 |
数据上分两层:
说话人层 分离标签(分离给的编号,不再改动)-> 匹配到的人(可为空,可解除)
文本层 模型原文(写入后不再改动)
当前文本(页面展示、人工修订、下游读取的都是它)
最后修订人、最后修订时间
落地要点:
- 按标签整批匹配,选一次人,这个标签下的全部句子一起更新;清空即解除。
- 候选只来自参会人员名单,前端下拉和后端校验两道都要有。
- 切段转写时,同一个人在不同分段是不同标签,要分别匹配或跨段合并。
- 修订只改可修订文本,原文不动,全文等派生数据随之重算。
完整版教学
一、分离和识别是两件事
说话人分离(Diarization)回答「谁在什么时候说话」里的「谁」,但它的「谁」是匿名的:模型把声音特征相近的句子聚成一类,按出现顺序编号。说话人识别(Identification)才回答「这个声音属于张三」,它需要事先知道张三的声音长什么样。
| 任务 | 输入 | 输出 | 需要提前准备 |
|---|---|---|---|
| 说话人分离 | 一段多人音频 | 每句的匿名编号 | 不需要 |
| 说话人识别 | 一段音频 + 已注册的声纹库 | 每句对应的具体身份 | 每个人的声纹样本 |
会议场景里,识别接口一般只提供分离。编号只是一个类别,纪要要写「张三提出」「李四反对」,就必须再做一步:把编号对到真人。
二、把标签对到真人的三条路
人工指认最直接:页面列出每个标签和它的几句发言,由人选出这是谁。准确率最高,成本是人工时间。
声纹比对把每个标签的音频提取成声纹向量,和已注册的声纹库算相似度,超过阈值就认定。它全自动,但前提重:每个人都要事先录一段声纹,而声纹属于生物特征信息,采集、存储、删除都要合规;同一个人换了麦克风、在嘈杂会议室、嗓子哑了,相似度都会下降。
内容推断让大模型读带标签的发言,从线索里推断身份:主持人说「下面请小周讲一下进度」,紧接着开口的标签多半就是小周;「我是财务部的刘洋」是自我介绍。它不需要声纹,还能给出判断依据,但线索不一定有,推断也可能错,只适合作为给人看的建议。
实际系统常常组合使用:模型或声纹先给建议,人确认后才写库。
三、按标签整批,而不是逐句
一小时的会议可能有 600 句、5 个说话人。逐句指认要选 600 次;按标签指认只要选 5 次,选一次就把这个标签下的一百多句一起更新。
逐句:600 句 × 每句 1 次选择 = 600 次
整批:5 个标签 × 每个标签 1 次选择 = 5 次
整批的前提是分离结果可信:同一个标签下确实是同一个人。分离也会出错,比如两个声音相近的人被聚成一类,或一个人被拆成两类。后一种情况按标签分别指认到同一个人就能合并;前一种情况整批匹配会把另一个人的话也算过来,需要逐句改说话人的能力兜底,代价是多一套逐句覆盖的数据结构。
四、候选范围为什么要收窄
公司通讯录可能有几千人,下拉框里全部列出来,选错的概率和查找成本都很高;让模型推断时,名单越长越容易编出一个看似合理的人。把候选限定在这场会议的参会人员,通常只剩几个到十几个。
收窄要前后端各做一次:前端下拉只列参会人员,是为了好选;后端再校验提交的人确实是这场会议的参会人员,是因为请求可以绕过页面直接发,不校验就可能把发言记到一个无关员工头上。模型给出的推荐同样要按名单过滤一遍,名单以外的人直接丢掉。
五、切段转写带来的标签不一致
长录音常常切成若干段分别识别,而分离是在每段内部做的,每段的编号都从 0 开始。于是同一个人在第 1 段是 01-0,在第 2 段可能是 02-1:
| 标签 | 发言片段数 | 实际是谁 |
|---|---|---|
01-0 | 42 | 张三 |
01-1 | 35 | 李四 |
02-0 | 38 | 李四 |
02-1 | 40 | 张三 |
处理办法有两种:一是把四个标签都列出来,人分别指认,张三选两次、李四选两次;二是跨段比对声纹,自动把 01-0 和 02-1 合成一类。前者简单可靠,后者省人工但依赖声纹质量。无论哪种,都不能因为编号相同就直接合并 01-0 和 02-0。
六、人工修订:原文和修订稿分开存
转写总会有错字,尤其是人名、产品名、专业术语。修订时如果直接覆盖模型输出,会丢掉三样东西:
| 丢掉的 | 后果 |
|---|---|
| 模型原文 | 无法对照改了哪里,改错了也回不去 |
| 修订人和时间 | 出了争议不知道是谁改的 |
| 原文与修订的差异 | 无法统计转写模型到底错了多少 |
所以每句转写存两份文本:模型原文写入后不再改动,当前文本是页面显示和人工修订的那一份,同时记下最后修订人和修订时间。下游一律读当前文本,原文只用于对照。
七、修订之后,派生数据要跟着刷新
转写句子往往还派生出别的数据:按顺序拼好的全文、基于全文生成的纪要、切片后写进向量库的索引。修订了一句话,这些派生数据就过期了:
修订一句 -> 按序号重新拼接全文(立即)
-> 已生成的纪要:不自动改,重新生成时才读到新文本
-> 检索索引:对应片段重新向量化
哪些立即刷新、哪些等下次重新生成,要按代价定:重拼全文只是字符串拼接,可以每次修订都做;重新生成纪要要调模型,还可能覆盖人工确认过的内容,应该由人决定什么时候做。
八、用修订衡量转写质量
保留原文之后,原文和修订稿的差异就是一份现成的人工标注。中文常用字错误率(CER)衡量:
CER = (替换 S + 删除 D + 插入 I) ÷ 参考文本字数 N
以修订稿为参考文本、模型原文为识别结果。例如原文「下周三把借口联条完」,修订为「下周三之前把接口联调完」,参考文本 11 个字:「借」改「接」、「条」改「调」是 2 处替换,原文漏掉的「之前」是 2 处删除,CER = (2 + 2 + 0) ÷ 11 ≈ 36%。
心法:分离给编号,匹配给身份,修订给正确的文本。三件事各存各的字段,谁也不覆盖谁:分离标签不改,模型原文不改,变的只有「匹配到的人」和「当前文本」。
按会议、按说话人统计修订率,能发现哪类场景转写最差,比如某个会议室的麦克风、某个口音重的发言人。
九、常见误区与追问
- 误区:打开了说话人分离,就知道每句话是谁说的。 分离只给匿名编号,身份要靠人工指认、声纹比对或内容推断。
- 误区:不同分段里编号相同的标签是同一个人。 编号在每段内部独立,
01-0和02-0可能是两个人,同一个人也可能是01-0和02-1。 - 误区:模型推荐的人直接写库,省掉人工。 内容推断依赖线索,可能错;推荐只作为建议,确认后才写库,名单外的推荐要过滤掉。
- 误区:候选范围只在前端下拉框里限制就够了。 请求可以绕过页面,后端必须再校验提交的人是这场会议的参会人员。
- 误区:修订时直接覆盖模型输出。 原文丢了就没法对照、回退和统计错误率,原文和修订稿要分开存。
- 追问:分离把两个人聚成了一个标签怎么办? 整批匹配会把另一个人的话也算过来,需要支持逐句改说话人;这种情况多发生在声音相近、发言很短的人身上。
- 追问:为什么不直接上声纹识别? 要事先采集声纹,涉及生物特征的采集和存储合规;设备、环境变化还会让相似度漂移,适合固定成员、固定会议室的场景。
十、加强记忆
先分清两个词:分离给匿名编号,识别给身份,会议转写默认只有分离。编号对到真人有三条路:人工指认最准,声纹比对全自动但要注册、有隐私负担,内容推断靠点名和自我介绍、只能当建议。写库按标签整批,一小时 600 句、5 个人,选 5 次而不是 600 次;候选收窄到参会人员,前端限制、后端校验、模型推荐按名单过滤三道。切段转写时同一个人在各段标签不同,01-0 和 02-1 可能是一个人,01-0 和 02-0 可能是两个人。修订存两份:原文不动,修订稿给下游用,记下修订人和时间;修订后全文立即重拼,纪要和索引按代价决定何时刷新;原文和修订稿的差异算 CER,就是现成的质量统计。
录音怎么切段转写、时间轴怎么拼、标签为什么带段号,见「会议录音怎么转写成带时间轴和说话人的文本?长录音怎么分段处理?」。
项目实战落地
项目里怎么做的
《AI Agent智能会议纪要辅助系统》在转写完成后的详情弹窗里放了两张表:上面是说话人表,下面是时间轴表。
- 说话人汇总。 详情接口把片段按标签归并,统计每个标签的发言片段数;表格顺序是各标签在时间轴上第一次出现的顺序。
- 按标签整批匹配。 在某个标签那一行的下拉框里选一位参会人员,这个标签下的全部片段一次写入同一个
speaker_user_id;清空下拉框就是解除匹配:
# 圈定这个任务下、这个标签的全部片段
label_query = TranscriptSegment.filter(task_id=task.id, speaker_label=payload.speaker_label)
if not await label_query.exists():
raise CustomException("说话人标签不存在")
# 传了用户时校验他确实是这场会议的参会人员,不能匹配到无关员工
if payload.user_id is not None:
if not await MeetingParticipant.filter(meeting_id=task.meeting_id, user_id=payload.user_id).exists():
raise CustomException("只能匹配该会议的参会人员")
# user_id 为 None 时相当于把这批片段的匹配清空
await label_query.update(speaker_user_id=payload.user_id)
- 跨段分别匹配。 录音超过 20 分钟被切成多段时,同一个人在不同分段里是不同的标签(例如
01-0和02-1),说话人表格里是两行,需要分别匹配。 - 时间轴显示。 说话人列匹配前显示「说话人 01-0」,匹配后显示姓名。不想逐个去听的,可以点「AI建议匹配」让模型读发言内容推荐人选,做法见「如何估计大模型回答的置信度?」。
- 逐条修订。 时间轴每一行都有「修订」按钮,修订只动三列:
| 字段 | 修订时 |
|---|---|
original_text | 不动,始终是模型的原始输出 |
content | 改成新文本,页面展示的就是它 |
revised_by、revised_time | 记下当前用户和修订时间 |
每次修订之后,按顺序号把全部片段的 content 重新拼接,覆盖转写任务上的全文 full_text。
为什么这样取舍
- 分离只知道哪几段是同一个人。 模型认不出这个人是谁,标签只是编号,所以要人来对上参会人员。
- 一个标签选一次。 同一个音频分段里,同一个人的发言被标成同一个编号,选一次就更新这个标签下的全部片段。
- 原文留着对照。
original_text写入后不再改动,用于和修订后的文本对照;页面和后续环节用的都是content。 - 全文跟着片段走。 全文是由片段拼出来的,片段改了不重拼,读全文的环节看到的还是旧文本。
面试官还会追问
- 匹配下拉框里的候选人员是怎么取的?已经停用的账号会出现吗?
- 匹配请求被后端拒绝时,页面上已经被改掉的下拉框怎么恢复成数据库里的值?
- 转写任务还在执行时能修订片段吗?为什么?
学完《AI Agent智能会议纪要辅助系统》,上面这些追问你都会迎刃而解。