ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

语音识别结果没标点?用BERT标点预测模型提升文本可读性

语音识别结果没标点?用BERT标点预测模型提升文本可读性 简介面向语音识别应用开发者提供一套基于PaddlePaddle训练的标点符号预测模型用于对ASR识别结果自动补全标点提升文本可读性与后处理效率。该模型可处理CTC与Attention等常见ASR输出适配带标点数据集训练减轻后续NLP环节负担。资源包为zip压缩格式共5个文件大小约250.85MB。其中model.pdmodel为模型结构文件model.pdiparams为预训练权重info.json记录配置信息vocab.txt为词表文件model.pdiparams.info包含参数附加信息。加载后可直接推理或迁移微调。目前已有4930人学习下载是语音后处理方向的热门资源。模型整合了CRF、LSTM或Transformer等标点预测思路通过PaddlePaddle实现训练与优化拿到资源后可快速将标点功能接入现有语音识别流程也可参考描述中的实践教程理解数据处理、模型评估与端到端集成思路。适合智能语音助手、会议转写等场景的中高级开发者直接使用或二次开发大幅降低标点恢复功能的落地门槛。 语音识别跑出来的文字一大段黏在一起没有逗号没有句号用户看了直接来一句“这玩意儿是AI生成的吗怎么读起来这么费劲”——这个场景我遇到太多次了。市面上绝大多数语音识别系统尤其是流式和非流式的自研方案输出的都是不带任何标点符号的裸文本。要解决这个问题不能靠ASR引擎自己修得在识别结果后面加一个专门的标点符号预测模型Punctuation Prediction Model用序列标注或生成式的方式把逗号、句号、问号给补回去。这篇就完整讲讲我在这类模型上的选型、实现和工程落地的经验。1. 为什么语音识别结果天生没有标点ASR的设计机制决定的很多人第一次接触ASR会默认“转写出来就该带标点啊就像人听写一样”。但实际做一遍就明白了这玩意儿不是模型的“小毛病”而是整个建模目标里压根没把标点当回事。以中文ASR为例常见的端到端方案ACLAttention-based Encoder-Decoder注意力编码器-解码器和CTCConnectionist Temporal Classification连接时序分类都把语音信号映射成文字序列。CTC那一票模型更是直接在音素或汉字级别上做对齐训练数据里根本不含标点标签预测的时候自然吐不出标点。Attention类模型虽然能学 “下一个字是什么”但绝大多数训练语料在预处理阶段就把标点剥掉了因为标点没有稳定的声学对应——你听一个人说话停顿时长、语气升降和标点之间并没有严格的映射关系同一句话停半秒可能是逗号也可能是句号模型学起来非常吃力干脆就忽略。还有一个现实原因是工程上的流式识别为了保证首字延迟低解码时需要在局部上下文里做贪婪搜索或带约束的beam search这种解码方式根本没有机会考虑“这个位置该不该来一个句号”。很多厂商把标点放到后处理阶段做就是这个原因——ASR只管把字吐准语法和可读性交给下一级。不过这里有个反例值得提OpenAI的Whisper家族模型在识别结果里是带标点的因为它是生成式模型训练数据包含大量带标点的转录文本标点作为目标序列的一部分被模型学到了。所以你要是用Whisper天然就有标点但如果你用的是自研或者开源的ASR网络比如WeNet、Paraformer这类后面不接标点模型结果就是干干净净的一长串字。说回到方案选型。我做过对比给同一段60秒的会议录音跑WeNet出来的转写是“然后我们这边的话呢就是需要去确认一下权限的问题然后才可以让技术团队去处理”这种。加了标点模型之后变成“然后我们这边的话呢就是需要去确认一下权限的问题然后才可以让技术团队去处理。”可读性天差地别。标点符号预测不是炫技是语音识别结果能真正交付给下游NLP任务的前提——机器翻译、信息抽取、摘要生成全都依赖正确的句子边界。2. 给文本补标点的几条路线规则、序列标注与生成式模型给一段不带标点的文本恢复标点看起来像小学数学题但这个领域实际有四种常见做法走的路子完全不同我先把它们的原理和适用场景讲清楚。2.1 纯规则和声学启发式方法最朴素的做法是拿VAD语音活动检测或者ASR解码过程中的停顿时长来做规则判断。比如检测到静音超过300毫秒就认为可能有逗号超过600毫秒认为可能到句末了或者根据语气词“呢”“啊”“吗”的位置补问号。这种方法实现快、零成本但准确率非常感人——因为语音停顿和标点没有严格对应关系有些人说话习惯性停顿多系统就会一逗到底有些人口语极其流利语义该换气了却没有任何停顿。我建议把规则方法当成兜底和辅助而不是主力。比如在静音检测后给候选位置标记一下候选标点类型再交给模型去修正这种“声学先验语义模型”的融合思路在实践中效果不错。2.2 基于BERT的序列标注模型主力方案现在工业界最主流的做法是把标点预测建模成一个token级别的序列标注任务。喂给模型的是不带标点的文本模型对每一个字符或每一个词输出一个标签标签空间可以定义为O不加标点、COMMA逗号、PERIOD句号、QUESTION问号需要的话还可以加EXCLAMATION感叹号。用BERT这类预训练语言模型做序列标注有个天然优势标点位置的实际决策依据不是局部的声学特征而是整句话的语义和句法结构。BERT通过注意力机制能把全句信息编码进每个token的向量判断一个位置是不是句末本质上是在问“到这里是不是一个完整语义单元”这恰好是BERT的强项。2.3 生成式大模型方案如果你已经引入了大模型LLM做文本后处理也可以顺手让它补标点。prompt写一句“请为下面这段文本添加合适的标点符号只输出标点后的文本”然后扔给模型就行。这个方案的优势是理解能力强还能兼顾格式整理比如顺带回车分段缺点是延迟高、成本高不适合流式场景而且长文本补标点时大模型偶尔会自作主张改写原句这在语音转写上属于不可接受的错误。2.4 端到端模型自带标点方案最后一类是Whisper这类带标点的端到端模型。如果你没有自研ASR的包袱项目从零开始直接用Whisper做识别标点就白捡了。但要注意Whisper的中文标点经常不稳定长句里该断的地方没断不该断的地方插个句号所以落地时通常也会配一个规则/模型后处理来修正。方案准确率延迟成本适用场景规则/声学启发式低极低极低兜底、辅路BERT序列标注高低单次推理毫秒级中生产主力适合流式/非流式大模型生成式很高高高离线批量文本后处理Whisper端到端中中文不稳中中从零搭建ASR且能接受二次修正如果让我直接给结论生产环境选BERT序列标注离线段落处理可以结合大模型兜底规则方法只用于辅助切句。3. 用BERT做标点恢复的完整实现数据、标签与训练细节下面重点讲怎么把一个BERT标点模型从零到一实现出来。这里我以中文场景为例因为中文没有英文大小写这种可以利用的线索难度更高一点。3.1 训练数据怎么搞标点预测模型的理想训练数据是“不带标点的文本 对应的带标点文本”配对。公开数据集有IWLST、TED Corpus、MuST-C等里面包含带标点的英文转录中文的话可以用一些开源语料比如THUCNews按标点切分后把标点去掉再造标签。但实际项目中最贴合场景的数据一定来自你自己的业务文本。这里我分享一个便宜好用的套路用Whisper给领域内的大量无标点文本做伪标注。就是先拿Whisper识别一批语音也可以直接把已有的历史ASR文本重新放给Whisper转写因为Whisper的输入可以是任意音频也可以直接在文本上搞但更常见的做法是转写音频得到带标点的结果再用对齐工具把无标点和有标点文本逐字对齐自动生成标签。这样能在没有人工标注的情况下快速攒出几十万条训练数据。3.2 标签体系与标注方案标签体系我建议保守一点O / COMMA / PERIOD / QUESTION 四类就够用。感叹号在口语里非常少而且模型容易和句号混淆先做进去只会增加训练难度。标注方案上有个细节容易踩坑标点符号是挂在它前面的token上还是后面的token上两种方案各有人用但实践中我更推荐把标点挂在它前面的最后一个非标点token上。原因很好懂——我们要预测的是“这个位置后面应该跟什么标点”建模目标落在了此前的词上预测时就能根据该token的上下文向量直接判断。代码里处理标签时先要在原始带标点文本上做一次token对齐。比如原始文本今天天气不错我们出去走走吧带标点的参考今天天气不错我们出去走走吧。按字对齐后标签序列就是[今天:O, 天气:O, 不错:COMMA, 我们:O, 出去:O, 走走:O, 吧:PERIOD]3.3 模型代码骨架模型部分用HuggingFace的Transformers库很简单核心代码如下from transformers import BertTokenizer, BertForTokenClassification, Trainer, TrainingArguments import torch # 标签映射 label2id {O: 0, COMMA: 1, PERIOD: 2, QUESTION: 3} id2label {v: k for k, v in label2id.items()} model_name bert-base-chinese tokenizer BertTokenizer.from_pretrained(model_name) model BertForTokenClassification.from_pretrained( model_name, num_labelslen(label2id), id2labelid2label, label2idlabel2id ) # 输入编码给每个字符打标签 def encode_example(text, labels): tokens list(text) # 中文按字拆 assert len(tokens) len(labels) encoding tokenizer( tokens, is_split_into_wordsTrue, truncationTrue, paddingmax_length, max_length128, return_tensorspt ) # 标签对齐subword粒度的标签映射到word_ids word_ids encoding.word_ids() aligned_labels [] previous_word_idx None for word_idx in word_ids: if word_idx is None: aligned_labels.append(-100) elif word_idx ! previous_word_idx: aligned_labels.append(label2id[labels[word_idx]]) else: # 同一个token的subword都继承同一标签 aligned_labels.append(label2id[labels[word_idx]]) previous_word_idx word_idx encoding[labels] torch.tensor(aligned_labels) return encoding训练时把带标点的句子通过上面的encode_example处理成input_ids labels丢给Trainer跑就行。注意BERT中文模型本身就是字粒度中文序列“is_split_into_wordsTrue”后word_ids基本还是逐字对齐不用额外做词法分析。3.4 训练策略里的两个关键点第一个是类别不均衡问题。真实语料里逗号占比远远超过句号和问号如果不处理模型会偏向把所有标点都预测成逗号。解决方法是给损失函数加类别权重class_weights torch.tensor([0.1, 1.0, 2.0, 3.0]) loss_fct torch.nn.CrossEntropyLoss(weightclass_weights)第二个是序列长度。中文BERT标点模型一般设置max_length128或者256长的话模型注意力效果下降、训练也慢。对于长文本我会在预处理时切成若干个短句片段切分策略不是机械地按字数“一刀切”而是尽量按照浅层的静音段、语义段来切比如先按300毫秒以上的静音断句再把每段切到100字以内送进模型。这个操作直接影响预测效果具体在下一节详细展开。4. 把标点模型接进语音识别Pipeline切句、滑窗与提速模型训出来只是第一步真正让人头大的是把模型接进ASR的实时或离线pipeline里。4.1 先把长文本切成合理片段ASR输出的文本可能是几十分钟的录音连在一起直接送进BERT会被截断尾部内容完全丢失。切分文本时我会分层处理第一层按VAD静音段切。流式ASR通常能给出每个语音片段的边界两个静音事件之间的文本天然成句倾向在这里切断对语义破坏最小。第二层按标点概率粗切。如果拿不到静音事件比如用的是第三方API返回的大段文本就先用一个轻量规则模型扫一遍在候选句子边界处切分。第三层按长度兜底。超过100个字符的长块强制拆开但尽量在逗号、连接词后面切。这套切法能从源头上减少BERT因为截断导致的标点误判。4.2 滑窗推理解决边界信息缺失切句不是完美的。如果切出来的第一个片段刚好落在句子中间模型就会丢失上文信息句首的标点预测大概率错。我的做法是用重叠滑窗窗口长度设为128 token步长设为96 token也就是每两个窗口之间有32 token的重叠。预测时只保留每个窗口中间64 token的标点结果窗口两侧的预测结果丢弃因为边界附近上下文不完整。这样还能顺带处理了长文本因为每次推理的输入长度固定、计算量稳定。滑窗推理在离线批量处理时很好用流式场景里则要改成“增量更新”策略每来一个语音分片就把前面的缓存文本加上当前文本一起预测但只取最后几个token的标点结果比如总在句号、问号出现时才判定一个完整句子的结束然后把缓存清空。4.3 推理提速与工程部署BERT-base在CPU上跑一段50字的文本一次前向推理大概要20到40毫秒GPU上能做到2到5毫秒。如果是离线转一场1小时的会议几十万字的文本跑起来用CPU可能要几分钟这个延迟在很多场景下不可接受。我常用的提速手段是ONNX Runtime导出加动态量化# 先把PyTorch模型导出为ONNX python -m transformers.onnx --model./punctuation_model --featuretoken-classification onnx/ # 用ONNX Runtime跑推理可以开int8量化进一步压缩实际测下来int8量化后的模型在CPU上的推理速度能提升3到4倍F1指标损失在1个百分点以内完全可以接受。部署服务我用的是FastAPI包一个HTTP接口输入是一段文本输出是加标结果。这里有个小技巧模型输出的是每个字符的label logits后处理时要做一次标点冲突消解——比如模型在连续两个token上都输出了COMMA就只保留置信度更高的那个如果模型预测句号时置信度低于阈值就降级成逗号这样能减少把语义完好的长句强硬拆开的问题。5. 实测中的典型翻车现场与排查链路这部分最值钱。做标点模型这一年多时间我碰到过不少问题有些问题不跑真实数据根本发现不了直接列出来分享给大家。5.1 逗号泛滥类别不均衡的典型表现第一个版本上线后用户反馈“怎么全是逗号”我看了一下预测结果长句中间确实被插满了逗号凡是有停顿的词后面都跟着一个COMMA。排查链路是这样的先检查label分布训练集里逗号接近7成句号只有2成问号更少。再看模型的类别精确率COMMA的precision很高但PERIOD的recall特别低。修复方法是两个一是加类别权重二是用Focal Loss让模型多关注难分类的句末位置。最有效的其实是数据层面的修复——在训练集里多补充短句、问句样本尤其是业务场景里的口语问句“可以吗”“行不行”“这样对不对”。修复后句号recall从61%涨到82%整体F1从74%涨到84%效果非常明显。5.2 切句位置导致的问号漏判有一段时间我发现一个诡异现象单独测一句“你觉得这样可以吗”模型能正确输出问号但整段录音跑下来这句话结尾的问号经常变成句号。定位了半天问题根本不在模型而在上游切句逻辑。我当时的切句策略是“超过100字截断优先在逗号处切”。结果这句话被切到上一个文本块末尾时恰好后面跟的是别的内容的片段BERT窗口右侧的语义被污染了模型判断这不是一个完整的问句。这是滑窗推理最容易踩的坑。解决办法流式场景里明确指示切句逻辑“当检测到疑问语气词吗、呢、怎么、能不能时强制延后切句直到batch结束”离线场景里重叠滑窗时只保留窗口中间部分不要贪心取到边界。5.3 口语填充词把标点位置带偏中文口语里充满了“嗯”“那个”“就是”“然后”这类填充词。模型特别容易在这些词后面加逗号导致整句话变得支离破碎。这个问题的根子在训练数据质量——如果训练语料里有大量带填充词的转写文本模型会认为“然后”后面必须跟逗号。我尝试过两种修复思路训练前做轻声词过滤把标注为“填充”的token在标签计算时置为ignore index或者把填充词替换成[FILL]特殊token后再训练让模型学会忽略它们。第二种思路效果更好一点因为预测时真实的填充词也会按同样方式被替换训练和推理的分布一致。5.4 中英文混排文本的全角半角问题如果你的业务里有英文夹杂比如“iPhone 15 pro max多少钱”BERT中文模型经常会给出COMMA然后又紧跟着一个全角逗号“”和一个半角逗号“,”并存的情况。这是因为中文模型对英文标点的空间不太敏感。解决方法是预处理时统一转成全角后处理时再按相邻语言自动调整或者在训练数据里加入20%的混排语料让模型见过这种分布。6. 总结一些经验与建议从我的项目经验来看标点恢复这个模块经常被当成“小任务”但做好了对整体体验的提升非常明显。它不是一个简单的二分类——不是“这里加不加标点”而是涉及语义边界判断、类别不均衡、上下文窗口、以及和ASR上游的协同。给后来的人几个建议。第一先别急着训练模型先把你业务里的文本分布摸清楚。你面对的到底是闲聊客服对话、会议纪要、还是口述文书不同场景下问句比例和句长分布完全不同这会直接影响标签体系和训练数据的配比。第二别把模型在公开测试集上的指标当成上线标准。我在TED Corpus上能到F1 88但业务场景里只有82差的这6个点全在口语词、方言音译词、重复字这些训练分布之外的东西上。上线前一定用真实历史数据做一个盲测。第三标点模型是纯文本后处理不改变识别内容本身但你最好在pipeline里保留“置信度低时放弃插入标点”的选项。宁可少一个逗号也不要多一个错误的句号——句号错误比逗号错误对语义的影响大得多尤其在下游接摘要和翻译时句子边界错了整个结果就塌了。最后如果业务真的要求省事现在也有不少厂商提供标点恢复的云API但你自己的数据、自己的语义自己训练的模型才最可控。这活儿不复杂但值得好好做。本文还有配套的精品资源点击获取
返回列表