4.1 文本后处理:标点、分段与口语清理

转写结果第一次落进库里的时候,我是有点失望的。一场一小时的周会,ASR 返回了一万两千字,准确率其实不错,术语词表加上之后专有名词基本都对。但这份文本没法直接看:好几句一逗到底看不到句号,一个人说的三句话被切成五段,满屏的那个、然后、就是说。老板扫了一眼就说,这比我手写纪要还累。

这一节要做的事很明确:后处理不是改正识别错误(那是第三篇术语词表和调优的活),而是把机器味很重的转写文本,清理成正常人能读的文本。这一步做扎实,后面 4.2 信息提取的准确率会高一截——提取模型面对的是通顺句子而不是碎片,能少很多误判。

清理这件事还有个隐形收益:它是整个工具链里唯一不需要花接口钱的环节。转写按时长计费,提取按调用次数计费,唯独清理是本地脚本跑的,一次写好反复用。所以在清理上多投入一点工程时间,回报是实打实的。

回头再看整个第四篇,清理是唯一一道纯本地、零成本的工序,也是最容易被低估的一道。写代码的同行经常问我要这套规则的开源版,其实规则本身不值钱,值钱的是每条规则背后那场会上踩的坑。

前置工序:空段与噪声过滤

跑清理规则之前,还有一道前置工序:把转写结果里的空段和纯噪声段先挑出来。我们的会议经常出现两种垃圾段,一种是开场调设备时的几十秒静音被识别成一串乱码段,另一种是会议室门开着、走廊说话被收进去的噪声段。这些段没有实际内容,留下来只会干扰后面的归并和提取。

过滤的判据有三条:整段字数少于 3 且无标点、整段平均置信度低于 0.5、段内没有任何动词或名词。三条满足任意一条,段就打上疑似噪声标记,不删除但隔离——隔离段的去向由 4.4 的校对人决定,脚本不擅自做主。这种把决定权留给人的设计,后来证明救过好几次次,有次一个关键补充意见就是夹在噪声段里被误标的。

转写原文的典型问题清单

先列一下我在我们会议转写稿里反复见到的问题,以及对应的清理规则。这张表是清理规则迭代到 v3 才稳下来的,前两版在过度清理上栽过跟头,后面细说。

问题类型 典型表现 清理规则 清理前后示例
缺标点 长句无停顿、一逗到底 按停顿时间戳在长句中补句号 那个我们下周把这个事情推一下 → 我们下周把这个事情推一下
语气词泛滥 每句开头都是那个、然后 语气词表匹配,仅删句首句尾 那个这个方案我觉得可以 → 这个方案我觉得可以
词语重复 我我我们、这个这个 连续相同短词合并为一个 我我我觉得可以 → 我觉得可以
句子碎片 半句一段,一段只有一两个词 按时间戳间隔和字数阈值归并 然后呢 / 对 / 就这样 → 并入上一段
口语断裂 说着说着换了个说法 保留后半句,删掉前半截 我觉得不对,应该是周五 → 应该是周五
语气词成段 嗯、啊、对单独占一行 整段只剩语气词则删除 嗯 / 对(单独成行)→ 删除

表里的规则不是一次定稿的,而是跑了一个月、对着十几次会议的清理日志逐条磨出来的。每条规则上线前,我都会拿最近三场会议的原文当测试集,清理完通读一遍,看有没有把语义清断。这个抽检习惯后来成了惯例,新人改规则也照这个流程走。

清理规则怎么实现

清理我优先用规则加脚本,没有一上来就全丢给大模型。原因是规则便宜、确定、可回滚,清理脚本跑在之前搭 Web 后端的那台云服务器上(底座搭建可以看我另一本腾讯云五件套实战),一万字的文本一两秒就跑完,还不花模型调用的钱。核心是几条正则,示意如下:

# 伪正则示意,实际按所用语言的规则库实现
RemoveFiller:    ^(那个|然后|就是|嗯|啊|对)[,。]    # 仅清理句首语气词
DedupeRepeat:    (.{1,4})\1{1,}                      # 连续重复的短词合并
MergeFragment:   段长 < 8 字 且 与上一段间隔 < 2 秒 → 并入上一段
DropSingleChar:  ^[嗯啊对哦]{1,3}$                    # 整段只有语气词则删除

规则执行的顺序有讲究:先做片段归并,再做语气词清理。如果反过来,碎片段先被清掉,上下文就散了,归并算法会错乱。这个顺序是交过学费才定下来的。

规则之外还有一类内容必须手工放行:会议里反复出现的确认句式(你刚才说的我没太听清,能再讲一遍吗)。这类句子对纪要没用,但它是发言人认真参与的证据,也是后期复盘讨论氛围的素材,清掉可惜。我们把它放进白名单,清理规则一律跳过。

段落归并策略

ASR 返回的分段是按发音停顿切的,和人讲话的段落不是一码事。归并策略我们分场景定,不搞一刀切:

场景 归并规则
一人连续发言 停顿小于 2 秒的相邻段直接合并
两人一来一往 按说话人标签切换切分,跨人不合并
插话密集区 插话保留为独立段,标注为补充发言
一个人长汇报 按时间戳每 60 至 90 秒兜底切一段
疑似噪声段 置信度低于阈值的整段打标记,不自动删

归并之后最好再通读抽检几段,确认说话人标签没有被错并——两段不同人的话被并成一段,是归并算法最容易犯的错,纪要里会出现一个人突然接了自己的话这种怪事。

归并策略也不是一天定型的。最早我只按时间戳间隔做合并,结果一上午的会议并成两个大段,读起来像两篇小作文;后来才引入说话人标签作为切分依据,又保留插话的独立性。策略表里的每条规则背后,基本都是一次具体的阅读投诉。

标点修复的分寸

补标点听着简单,其实最难拿捏。我们的做法是不真补标点,而是只补句号:ASR 返回的文本往往一逗到底,一句话能抻到五六十个字。规则按停顿时间戳找大于 1.5 秒的空隙,若前后又是完整主谓结构,就在那里断句。逗号、问号、感叹号一律不动——这些需要理解语气,规则硬补只会帮倒忙,读起来反而别扭。

分号更是禁区。有阵子我为了合并相关短句试过分号,结果一段里挤了四五个分号,比没有标点还难读。后来定下原则:转写文本的标点修复只做减法(断长句)不做加法(猜语气)。语气的活交给读的时候自己感受,纪要又不是文学作品。

标点修复还有一处容易遗忘:人名和项目名后面的停顿。转写里张三那个模块做完了中间停顿长,模型给了一个逗号,读起来像张三、那个模块。这类没有专门规则,统一丢进校对环节让眼睛过一遍,成本可接受。

清理流水线的工程实现

整条清理流水线挂在转写回调后面,转写结果一落库就自动触发。流水线分五步串行执行,每步的输入输出都留痕:

步骤 动作 出错处理
取文本 从 COS 拉转写结果 重试三次,失败告警
清理 依次跑四条规则 单条规则报错跳过并记录
归并 按场景策略并段 保留原分段兜底
存档 清理稿与原文同目录存放 只写新文件不覆盖
通知 更新任务状态等下游提取 不阻塞主流程

流水线的细节,比如云函数的事件触发和重试配置,第五篇会展开讲。这里只强调一个设计要点:每一步都是幂等的,同一条数据跑两次结果一样。这样遇上重试、补跑都不怕,调试的时候也敢拿真实数据反复试。

也有同事问过,清理为什么要分这么多步而不是一把梭。原因是可解释性:分步之后,哪一步出问题定位到哪一步,改起来不会牵一发动全身。那阵子正好在折腾本地服务器做归档对比,NAS 家庭服务器的分层存储思路给了我启发——每层职责单一,整条链路才稳。

一次过度清理的教训

第一版清理规则上线的第二天,产品同事就找来了:一段重要讨论的纪要读起来莫名其妙。查原文才发现,那次讨论里反复出现那个……对、那个……对的确认句式,大概是会议室信号不好,两边在互相确认有没有听清。我写的一条规则把这种整段删掉了,结果前一句提的问题、后一句给的回答,中间的衔接全没了,语义断成两截。

教训很具体:清理必须保留可回溯。每一步删了什么、删了多少,都记一份清理日志,和原文一起存在 COS 里,出问题先拿原文比对再调规则。从那以后我给自己定了两条规矩:第一,宁可少删不可误删,拿不准的语气词先留着,交给后面的文本模型在提取阶段忽略——模型工具的选型可以对照DeepSeek 工具链看;第二,清理后的文本绝不覆盖原文,两者都存,随时能比对差异。本地音频另外留一份的方案,可以参考NAS 家庭服务器的做法,和云端互为备份。

后来我把清理规则按置信度做了分档:高置信度段落跑全量规则,中置信度只跑合并和去重,低置信度只做标记不清理。同一套规则对不同质量段落使不同力度,过度清理的事再没出现第二次。说到底,清理和看病一个道理:治不了的先观察,别急着下刀。

延伸阅读