为什么结果带标签?SenseVoiceSmall rich_transcription后处理详解
1. 你听到的不只是文字,而是“有情绪的声音”
打开 SenseVoiceSmall 的 WebUI,上传一段录音,点击识别——几秒后,屏幕上跳出的不是干巴巴的一行字,而是像这样:
“今天项目上线了!<|HAPPY|>大家辛苦了<|APPLAUSE|>记得早点休息<|SAD|>”
你可能会愣一下:这括号里的<|HAPPY|>是什么?是 bug 吗?还是模型“说漏嘴”了?
其实,这恰恰是 SenseVoiceSmall 最特别的地方:它不只做语音转文字(ASR),更在做语音理解(Speech Understanding)。那些带尖括号的标签,不是错误,而是模型主动“听懂”的证据——它识别出了说话人语气里的开心、现场响起的掌声、甚至后半句突然低落的情绪。
很多刚接触的朋友会下意识想:“能不能去掉这些标签?看着太乱。”
但真正用过的人很快会发现:删掉标签,就等于关掉了模型的‘情感耳朵’和‘环境眼睛’。
本文不讲抽象原理,不堆参数指标,就带你从一行输出出发,搞清楚:
- 这些标签从哪来?
- 为什么默认保留而不是自动隐藏?
rich_transcription_postprocess到底做了什么?- 怎么根据你的需求,灵活控制“显示多少”“怎么显示”“要不要显示”。
读完你能自己改代码,让结果既保留关键信息,又干净易读。
2. 标签不是噪音,是富文本结构的“骨架”
2.1 富文本识别(Rich Transcription)到底是什么?
传统语音识别(比如 Paraformer、Whisper)的目标很明确:把声音变成最接近原话的文字。它追求“准”,但不管“味儿”。
而 SenseVoiceSmall 的rich transcription(富文本转录),目标是还原一段语音的完整表达意图。它把语音拆解成三层信息:
| 层级 | 内容 | 示例 |
|---|---|---|
| 文字层 | 说话内容本身 | “大家辛苦了” |
| 情感层 | 说话时的情绪状态 | `< |
| 事件层 | 环境中发生的非语音事件 | `< |
这三者组合起来,才构成一句“有血有肉”的富文本。就像你看剧本,不仅要看台词,还要看括号里的【微笑】【拍桌】【背景音乐渐起】——这些才是让文字活起来的关键注释。
2.2 标签格式为什么长这样?<|XXX|>的设计逻辑
你可能注意到,所有标签都统一用<|xxx|>包裹,比如<|HAPPY|>、<|BGM|>。这不是随意选的符号,而是经过深思熟虑的工程选择:
- 高区分度:
<|和|>在自然语言中几乎不会出现,极大降低误匹配风险。对比<happy>容易和单词happy混淆,而<|HAPPY|>几乎不可能被当成普通词汇。 - 可解析性强:正则表达式
r"<\|(.*?)\|>"一行就能精准提取所有标签,无歧义、无嵌套、无逃逸问题。 - 语义清晰:
<|像一个“开始标记”,|>像一个“结束标记”,中间大写英文名直白表明类型,开发者一眼看懂。 - 预留扩展性:未来加新类型(比如
<|COUGH|>、<|DOOR_CLOSE|>),格式完全兼容,无需改解析逻辑。
所以,这些标签不是“没处理完的中间产物”,而是模型输出的标准结构化字段——就像 JSON 里的 key,是设计好的接口契约。
3.rich_transcription_postprocess:从“机器输出”到“人能读”的翻译器
3.1 它不是“美化工具”,而是“语义重写引擎”
很多人以为rich_transcription_postprocess就是把<|HAPPY|>替换成“(开心)”这种简单替换。错了。
它的核心任务是:把模型原始输出的富文本字符串,按人类阅读习惯,重组织为更自然、更紧凑、更少干扰的表达形式。
我们来看它实际做了什么(基于funasr源码逻辑简化说明):
# 原始模型输出(raw_text) raw = "测试一下<|HAPPY|>效果怎么样<|APPLAUSE|>" # rich_transcription_postprocess 处理后(clean_text) clean = "测试一下(开心)效果怎么样(掌声)"它不只是替换,还做了三件关键事:
- 合并相邻标签:如果连续出现多个
<|HAPPY|><|HAPPY|>,只保留一个; - 智能位置插入:标签不硬塞在词尾,而是尽量贴近它修饰的语义单元。比如
<|HAPPY|>谢谢大家→谢谢大家(开心),而不是谢谢(开心)大家; - 语义降噪:过滤掉置信度低、或上下文明显冗余的标签(如静音段里的
<|SILENCE|>默认不输出)。
更重要的是:它不改变原始信息,只改变呈现方式。你永远可以通过关闭这个函数,拿到最原始的<|xxx|>字符串,用于后续程序解析。
3.2 源码级解读:它到底怎么工作的?
funasr.utils.postprocess_utils.rich_transcription_postprocess的核心逻辑非常轻量,不到 50 行 Python,却覆盖了绝大多数实用场景。我们拆解关键步骤:
import re def rich_transcription_postprocess(text): # 步骤1:提取所有标签及位置 tags = list(re.finditer(r"<\|(.*?)\|>", text)) # 步骤2:对每个标签,生成人类可读的替代文本 replacements = [] for match in tags: tag_name = match.group(1).strip() # 情感类 → 中文括号 + 情绪词 if tag_name in ["HAPPY", "ANGRY", "SAD", "NEUTRAL"]: replacement = f"({tag_name.lower().capitalize()})" # 事件类 → 中文括号 + 事件名 elif tag_name in ["APPLAUSE", "LAUGHTER", "BGM", "CRY"]: mapping = { "APPLAUSE": "掌声", "LAUGHTER": "笑声", "BGM": "背景音乐", "CRY": "哭声" } replacement = f"({mapping.get(tag_name, tag_name)})" else: replacement = f"({tag_name})" replacements.append((match.start(), match.end(), replacement)) # 步骤3:从后往前替换(避免索引偏移) result = text for start, end, rep in reversed(replacements): result = result[:start] + rep + result[end:] return result看到这里你就明白了:它本质是一个规则驱动的模板映射器,没有调用大模型,不依赖 GPU,纯 CPU 运行,毫秒级完成。这也是为什么它能集成在实时 WebUI 里,完全不影响响应速度。
4. 实战:三种常见需求,对应三种改法
光知道原理不够,你真正需要的是:遇到具体问题,马上能动手改。下面三个真实高频场景,附可直接运行的修改方案。
4.1 需求一:我要纯文字,彻底去掉所有标签(含括号)
适用场景:导出给不熟悉标签的同事看;接入已有 NLP 流程(如关键词提取),怕标签干扰分词。
解决方案:跳过 postprocess,直接取原始 text
# 修改前(带后处理) clean_text = rich_transcription_postprocess(raw_text) # 修改后(纯原始输出) clean_text = res[0]["text"] # 直接用模型原始输出注意:此时结果仍是<|HAPPY|>你好<|APPLAUSE|>这种格式。如果你连括号都不要,只需一行正则清洗:
import re clean_text = re.sub(r"<\|.*?\|>", "", raw_text).strip() # 输出:"你好"4.2 需求二:我要保留标签,但改成更简洁的符号(如 😊 )
适用场景:做内部演示、社交分享,希望结果更活泼直观;或前端展示时用图标替代文字。
解决方案:自定义映射字典,替换rich_transcription_postprocess的内部逻辑
# 在 app_sensevoice.py 开头添加 CUSTOM_EMOJI_MAP = { "HAPPY": "😊", "ANGRY": "😠", "SAD": "😢", "APPLAUSE": "", "LAUGHTER": "😄", "BGM": "🎵", "CRY": "😭" } # 修改 sensevoice_process 函数中的后处理部分: def sensevoice_process(audio_path, language): # ... 前面不变 ... if len(res) > 0: raw_text = res[0]["text"] # 自定义 emoji 映射版 clean_text = raw_text for tag, emoji in CUSTOM_EMOJI_MAP.items(): clean_text = clean_text.replace(f"<|{tag}|>", emoji) return clean_text # ... 后面不变 ...效果:<|HAPPY|>太棒了<|APPLAUSE|>→😊太棒了
4.3 需求三:我只要情感标签,不要事件标签(或反之)
适用场景:客服质检只关注情绪波动;视频剪辑只关心 BGM/掌声时间点,不关心说话人情绪。
解决方案:在 postprocess 前,先过滤掉不需要的标签类型
import re def filter_tags_by_type(text, keep_types=("HAPPY", "ANGRY", "SAD")): """只保留指定类型的标签,其余全部清除""" # 先找出所有标签 all_tags = re.findall(r"<\|(.*?)\|>", text) # 构建要删除的标签集合(不在 keep_types 中的) to_remove = set(all_tags) - set(keep_types) # 逐个删除 result = text for tag in to_remove: result = result.replace(f"<|{tag}|>", "") return result # 在 sensevoice_process 中使用: if len(res) > 0: raw_text = res[0]["text"] # 只保留情感类标签 filtered_text = filter_tags_by_type(raw_text, keep_types=["HAPPY", "ANGRY", "SAD"]) clean_text = rich_transcription_postprocess(filtered_text) return clean_text这样,输入<|HAPPY|>开会<|BGM|>请发言<|APPLAUSE|>
→ 过滤后<|HAPPY|>开会请发言
→ 后处理后开会请发言(开心)
5. 进阶技巧:标签不只是“显示”,还能“驱动业务逻辑”
富文本标签的价值,远不止于让结果更好看。当你理解了它的结构,就能把它变成业务流程的“触发开关”。
5.1 场景:自动切分“情绪片段”,用于教学反馈
老师录音讲课,你想自动标出哪些段落学生容易走神(长时间无掌声/笑声)、哪些段落情绪高涨(连续 HAPPY + APPLAUSE)。
实现思路:用标签位置做时间锚点(需配合model.generate的timestamp输出)
# 启用时间戳(修改 generate 调用) res = model.generate( input=audio_path, language=language, use_itn=True, batch_size_s=60, merge_vad=True, merge_length_s=15, output_timestamp=True, # 关键:开启时间戳 ) # res[0] 现在包含 'text' 和 'timestamp' 字段 # timestamp 格式示例:[(0.2, 1.5), (1.6, 2.8), ...] 对应每个 token 的起止时间 # 结合标签位置,就能算出 `<|HAPPY|>` 发生在第几秒有了时间戳,你就能:
- 统计每分钟 HAPPY 出现次数 → 评估课堂互动热度;
- 找出连续 5 秒无任何事件标签的段落 → 推荐此处插入提问;
- 标记
<|ANGRY|>后 2 秒内的文字 → 自动生成“情绪预警”摘要。
5.2 场景:用标签做音频二次编辑指令
把<|BGM|>当作“此处插入背景音乐”的标记,<|APPLAUSE|>当作“此处淡入掌声音效”的指令,直接驱动 FFmpeg 自动混音:
# 伪代码:检测到 `<|BGM|>` 就在对应时间点叠加 bgm.mp3 ffmpeg -i input.wav -i bgm.mp3 -filter_complex \ "[0:a]atrim=0:10[a0]; [1:a]atrim=0:5[bgm]; [a0][bgm]amix=inputs=2:duration=first" \ output.wav标签,从此不再是“结果的一部分”,而是“下一步动作的说明书”。
6. 总结:标签是能力的出口,不是需要掩盖的瑕疵
回到最初的问题:为什么结果带标签?
现在你应该清楚了:
- 它不是模型没“做完”,而是模型刚刚“做完第一层”——把声音解构成结构化语义;
rich_transcription_postprocess不是“修复工具”,而是“人机接口翻译器”,负责把机器语言转成人话;- 你不需要接受它默认的样子,你可以删、可以换、可以筛、可以驱动业务——控制权始终在你手上。
真正的技术自由,不是“用得越简单越好”,而是“想怎么用,就怎么用”。SenseVoiceSmall 把富文本能力开放给你,而这篇详解,就是帮你握住那把钥匙。
下次再看到<|HAPPY|>,别急着删。停下来想一想:
它能帮你省下多少人工标注时间?
它能让你的下游系统多聪明一分?
它是不是,本该是你产品里最不起眼、却最关键的那行代码?
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。