如何定制化输出格式?修改SenseVoiceSmall返回结构教程
1. 背景与需求分析
随着语音理解技术的发展,传统“语音转文字”已无法满足复杂场景下的语义理解需求。阿里巴巴达摩院推出的SenseVoiceSmall模型在多语言识别的基础上,进一步支持情感识别(如开心、愤怒)和声音事件检测(如掌声、BGM),实现了富文本转录(Rich Transcription)能力。
然而,在实际工程应用中,原始输出格式往往不符合业务系统的要求。例如:
- 前端需要 JSON 结构而非纯文本;
- 需要分离出时间戳、情感标签、事件类型等字段;
- 要求去除或重命名特定标签以适配下游 NLP 处理模块。
因此,如何定制化 SenseVoiceSmall 的返回结构,成为提升其落地可用性的关键一步。
本文将围绕这一核心问题,详细介绍从模型输出解析到结构重组的完整流程,并提供可运行代码示例,帮助开发者快速实现个性化输出格式改造。
2. 理解原始输出结构
2.1 默认输出格式解析
当调用model.generate()后,SenseVoiceSmall 返回的是一个包含多个段落结果的列表,每个元素为字典结构:
[ { "text": "<|zh|><|HAPPY|>今天天气真好啊<|LAUGHTER|>哈哈<|END|>", "timestamp": [[0.1, 0.8], [0.9, 1.5]] } ]其中:
text字段包含带特殊标记的富文本内容;- 标记分为三类:语言标识(
<|zh|>)、情感/事件标签(<|HAPPY|>)、控制符(<|END|>); timestamp提供每句话的时间区间(单位:秒)。
2.2 富文本后处理机制
FunASR 提供了内置函数rich_transcription_postprocess()对原始文本进行清洗,主要功能包括:
- 移除
<|xxx|>类似标签; - 将情感标签转换为自然语言描述(如
[开心]); - 合并相邻片段。
但该函数仅输出字符串,不保留结构化信息,限制了高级应用场景。
3. 定制化输出结构设计与实现
3.1 目标输出结构定义
我们希望最终返回如下 JSON 结构:
{ "segments": [ { "start": 0.1, "end": 0.8, "text": "今天天气真好啊", "emotion": "HAPPY", "events": ["LAUGHTER"] } ], "language": "zh" }此结构具备以下优势:
- 时间戳独立暴露,便于视频同步;
- 情感与事件分类提取,利于情绪分析;
- 支持多段落分片管理。
3.2 自定义解析器开发
下面实现一个完整的结构化解析类CustomSenseVoiceParser:
import re from typing import List, Dict, Any class CustomSenseVoiceParser: def __init__(self): # 匹配所有 <|...|> 格式的标签 self.tag_pattern = r"<\|([^|]+)\|>" # 已知的情感标签集合 self.emotion_tags = {"HAPPY", "SAD", "ANGRY", "NEUTRAL"} # 已知的声音事件标签 self.event_tags = {"BGM", "APPLAUSE", "LAUGHTER", "CRY"} def parse(self, raw_text: str, timestamps: List[List[float]]) -> Dict[str, Any]: """ 解析原始输出,生成结构化结果 """ tokens = self._tokenize(raw_text) segments = [] current_segment = {"text": "", "events": []} lang = "auto" for token in tokens: if token in self.emotion_tags: current_segment["emotion"] = token elif token in self.event_tags: current_segment["events"].append(token) elif token.startswith("zh") or token in ["en", "yue", "ja", "ko"]: lang = token elif token == "END": if current_segment["text"].strip(): segments.append(current_segment) current_segment = {"text": "", "events": []} else: # 普通文本 current_segment["text"] += token # 处理最后一个片段 if current_segment["text"].strip() and len(segments) < len(timestamps): start, end = timestamps[len(segments)] current_segment["start"] = round(start, 3) current_segment["end"] = round(end, 3) segments.append(current_segment) return { "language": lang, "segments": segments } def _tokenize(self, text: str) -> List[str]: """ 分词:保留非标签部分作为普通文本 """ parts = re.split(self.tag_pattern, text) tokens = [] i = 0 while i < len(parts): if i % 2 == 0: # 偶数索引是普通文本 if parts[i].strip(): tokens.append(parts[i]) else: # 奇数索引是标签内容 tokens.append(parts[i]) i += 1 return tokens3.3 集成至 Gradio 接口
修改原app_sensevoice.py中的处理函数,使用自定义解析器替代默认后处理:
# 初始化解析器 parser = CustomSenseVoiceParser() def sensevoice_process(audio_path, language): if audio_path is None: return {"error": "请上传音频文件"} res = model.generate( input=audio_path, cache={}, language=language, use_itn=True, batch_size_s=60, merge_vad=True, merge_length_s=15, ) if not res: return {"error": "识别失败"} # 提取原始文本与时间戳 raw_text = res[0]["text"] timestamps = res[0].get("timestamp", []) # 使用自定义解析器 structured_result = parser.parse(raw_text, timestamps) return structured_result此时,前端接收到的结果即为结构化 JSON,可直接用于数据存储或 API 返回。
4. 输出格式扩展建议
4.1 添加中文语义映射
为提高可读性,可在输出中增加中文说明:
EMOTION_ZH = { "HAPPY": "开心", "SAD": "悲伤", "ANGRY": "愤怒", "NEUTRAL": "平静" } EVENT_ZH = { "BGM": "背景音乐", "APPLAUSE": "掌声", "LAUGHTER": "笑声", "CRY": "哭声" }并在parse()方法中添加字段:
current_segment["emotion_zh"] = EMOTION_ZH.get(token) current_segment["event_zh"] = [EVENT_ZH[e] for e in current_segment["events"]]4.2 支持多种输出模式切换
可通过参数控制输出格式类型:
def sensevoice_process(audio_path, language, output_format="structured"): # ...原有逻辑... if output_format == "text": clean_text = rich_transcription_postprocess(raw_text) return {"text": clean_text} elif output_format == "json": return structured_result else: return {"raw": raw_text}同时在 Gradio 界面中增加输出格式选择项:
format_dropdown = gr.Dropdown( choices=["structured", "text", "raw"], value="structured", label="输出格式" )5. 实践中的常见问题与优化
5.1 时间戳对齐不准问题
由于 VAD(语音活动检测)与 ASR 分段策略不同步,可能导致时间戳与文本片段数量不一致。
解决方案:
- 使用
merge_vad=False关闭自动合并,获取更细粒度分段; - 或手动插值补全缺失的时间戳区间。
5.2 多情感标签处理
某些句子可能连续出现多个情感标签(如<|HAPPY|><|SAD|>),需决定优先级。
建议策略:
- 取最后一个有效情感(表示当前语气);
- 或记录所有出现过的情感,用于长周期情绪趋势分析。
5.3 性能优化建议
- 缓存模型实例,避免重复加载;
- 对长音频采用分块处理 + 异步合并;
- 使用
batch_size_s控制内存占用,防止 OOM。
6. 总结
通过对 SenseVoiceSmall 模型输出结构的深入剖析与重构,本文展示了如何将其默认的富文本输出转化为高度结构化的 JSON 数据格式,满足企业级应用对数据规范性和可集成性的要求。
核心要点总结如下:
- 理解原始输出机制:掌握
<|xxx|>标签体系及其语义含义; - 构建结构化解析器:基于正则分词与状态机逻辑,精准提取情感、事件、时间信息;
- 灵活适配业务需求:支持文本、JSON、原始等多种输出模式;
- 解决落地难题:针对时间戳错位、多标签冲突等问题提出实用对策。
通过上述方法,开发者不仅能实现输出格式的自由定制,还能为后续的情绪分析、内容审核、智能剪辑等高级功能打下坚实基础。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。