ASR后处理优化:SenseVoice-Small ONNX输出富文本清洗与格式化教程
1. 教程概述
1.1 学习目标
本教程将手把手教你如何处理SenseVoice-Small语音识别模型输出的富文本结果。学完后你将能够:
- 理解SenseVoice模型输出的富文本结构
- 掌握清洗和格式化识别结果的核心方法
- 将原始输出转换为清晰易读的文本格式
- 处理多语言混合场景下的特殊字符和标点
1.2 前置知识
不需要深厚的语音识别背景,只要具备以下基础就能轻松上手:
- 基本的Python编程经验
- 了解过JSON数据格式
- 会用文本处理的基本方法
1.3 教程价值
SenseVoice-Small模型识别效果很出色,但原始输出包含大量元数据(情感标签、事件标记、时间戳等),直接使用不太方便。通过本教程,你能快速掌握清洗技巧,让识别结果更适合实际应用场景。
2. 环境准备与快速部署
2.1 安装必要依赖
首先确保你的环境已经安装了以下Python包:
pip install modelscope gradio numpy pandas2.2 加载SenseVoice-Small模型
使用ModelScope加载量化后的ONNX模型非常简单:
from modelscope.pipelines import pipeline from modelscope.utils.constant import Tasks # 创建语音识别管道 asr_pipeline = pipeline( task=Tasks.auto_speech_recognition, model='damo/speech_sensevoice_small_asr-zh-cn-16k-common-vocab8358-tensorrt1', model_revision='v1.0.2' )3. 理解SenseVoice输出结构
3.1 原始输出格式解析
SenseVoice模型的输出不是简单文本,而是包含丰富信息的JSON结构:
# 典型输出示例 raw_output = { 'text': '你好世界<laugh>哈哈</laugh>今天天气真好', 'timestamp': [[0.0, 1.2], [1.2, 2.5], [2.5, 4.0]], 'emotion': {'text': '中性', 'score': 0.85}, 'events': [ {'type': 'laugh', 'start': 1.2, 'end': 2.5, 'score': 0.92} ], 'language': 'zh-cn' }3.2 富文本标签说明
模型使用特殊标签来标记各种事件和情感:
<laugh></laugh>:笑声片段<applause></applause>:掌声<music></music>:音乐片段<emotion type="高兴">:情感标记
4. 基础清洗与格式化方法
4.1 提取纯净文本
最简单的需求是只保留文字内容,去掉所有标签:
import re def extract_clean_text(rich_text): """移除所有富文本标签,只保留纯文本""" # 移除事件标签 clean_text = re.sub(r'<[^>]+>', '', rich_text) # 移除情感标签 clean_text = re.sub(r'\[emotion[^\]]*\]', '', clean_text) # 合并多余空格 clean_text = re.sub(r'\s+', ' ', clean_text).strip() return clean_text # 使用示例 rich_text = "你好<laugh>哈哈</laugh>世界" clean_text = extract_clean_text(rich_text) # 输出: "你好世界"4.2 保留事件信息的格式化
如果你需要保留事件信息但用更友好的方式展示:
def format_with_events(rich_text): """将事件标签转换为更易读的格式""" # 替换笑声标签 formatted = rich_text.replace('<laugh>', '[笑声]') formatted = formatted.replace('</laugh>', '') # 替换掌声标签 formatted = formatted.replace('<applause>', '[掌声]') formatted = formatted.replace('</applause>', '') # 替换音乐标签 formatted = formatted.replace('<music>', '[背景音乐]') formatted = formatted.replace('</music>', '') return formatted # 使用示例 rich_text = "欢迎<applause>鼓掌</applause>来到现场" formatted = format_with_events(rich_text) # 输出: "欢迎[掌声]来到现场"5. 高级处理技巧
5.1 处理多语言混合文本
SenseVoice支持50多种语言,处理混合语言文本时需要特别注意:
def handle_multilingual_text(text, primary_lang='zh-cn'): """ 处理多语言混合文本 primary_lang: 主要语言,用于决定标点符号和空格处理 """ if primary_lang in ['zh-cn', 'ja', 'ko']: # 中文、日文、韩文:全角标点,不加空格 text = re.sub(r'([。,!?;:”“‘’()])', r' \1 ', text) else: # 英文等西方语言:半角标点,加空格 text = re.sub(r'([.,!?;:"()])', r' \1 ', text) # 统一处理多余空格 text = re.sub(r'\s+', ' ', text).strip() return text5.2 时间戳对齐与文本分段
结合时间戳信息进行智能分段:
def segment_with_timestamps(text, timestamps, max_segment_length=50): """根据时间戳和文本长度进行智能分段""" segments = [] current_segment = "" current_start = timestamps[0][0] if timestamps else 0 words = text.split() for i, word in enumerate(words): if len(current_segment) + len(word) > max_segment_length and current_segment: # 当前分段达到最大长度,保存并新建分段 segments.append({ 'text': current_segment.strip(), 'start': current_start, 'end': timestamps[i-1][1] if i > 0 else current_start + 1.0 }) current_segment = word current_start = timestamps[i][0] if i < len(timestamps) else segments[-1]['end'] else: current_segment += " " + word if current_segment else word # 添加最后一个分段 if current_segment: segments.append({ 'text': current_segment.strip(), 'start': current_start, 'end': timestamps[-1][1] if timestamps else current_start + 1.0 }) return segments6. 完整后处理管道
6.1 构建端到端处理流程
将上述方法组合成完整的后处理管道:
class SenseVoicePostProcessor: def __init__(self, config=None): self.config = config or { 'remove_tags': True, 'format_events': False, 'max_segment_length': 50, 'primary_language': 'zh-cn' } def process(self, asr_result): """处理ASR识别结果""" text = asr_result.get('text', '') # 根据配置选择处理方式 if self.config['remove_tags']: processed_text = extract_clean_text(text) elif self.config['format_events']: processed_text = format_with_events(text) else: processed_text = text # 处理多语言混合 processed_text = handle_multilingual_text( processed_text, self.config['primary_language'] ) # 如果需要分段 if 'timestamp' in asr_result: segments = segment_with_timestamps( processed_text, asr_result['timestamp'], self.config['max_segment_length'] ) result = { 'full_text': processed_text, 'segments': segments, 'emotion': asr_result.get('emotion'), 'events': asr_result.get('events', []) } else: result = {'text': processed_text} return result # 使用示例 processor = SenseVoicePostProcessor({ 'remove_tags': True, 'format_events': False, 'max_segment_length': 40, 'primary_language': 'zh-cn' }) processed_result = processor.process(asr_output)6.2 Gradio界面集成
将后处理功能集成到Gradio界面中:
import gradio as gr def recognize_and_process(audio_path): """识别音频并处理后处理""" # 语音识别 raw_result = asr_pipeline(audio_path) # 后处理 processor = SenseVoicePostProcessor() processed_result = processor.process(raw_result) return processed_result['full_text'] # 创建Gradio界面 iface = gr.Interface( fn=recognize_and_process, inputs=gr.Audio(type="filepath"), outputs=gr.Textbox(label="处理后的文本"), title="SenseVoice语音识别与后处理", description="上传音频文件,获取清洗后的识别文本" ) if __name__ == "__main__": iface.launch(server_name="0.0.0.0", server_port=7860)7. 实用技巧与进阶建议
7.1 处理常见问题
在实际使用中可能会遇到的一些问题及解决方法:
def handle_common_issues(text): """处理常见识别问题""" # 修复常见的中英文标点混淆 punctuation_map = { ' ,': ',', ' .': '.', ' ?': '?', ' !': '!', ' ;': ';', ' :': ':', '“ ': '"', ' ”': '"', "‘ ": "'", " ’": "'", "( ": "(", " )": ")" } for wrong, correct in punctuation_map.items(): text = text.replace(wrong, correct) # 修复多余的空格 text = re.sub(r'(\w) (\w)', r'\1\2', text) # 移除单词间的空格(中文) text = re.sub(r'([a-zA-Z]) ([a-zA-Z])', r'\1\2', text) # 移除英文单词间空格 return text7.2 性能优化建议
处理大量音频时的性能优化技巧:
import threading class BatchProcessor: """批量处理优化类""" def __init__(self, max_workers=4): self.max_workers = max_workers self.processor = SenseVoicePostProcessor() def process_batch(self, audio_paths): """批量处理多个音频文件""" results = [] lock = threading.Lock() def process_single(path): try: raw_result = asr_pipeline(path) processed = self.processor.process(raw_result) with lock: results.append({'path': path, 'result': processed}) except Exception as e: print(f"处理 {path} 时出错: {e}") threads = [] for path in audio_paths: thread = threading.Thread(target=process_single, args=(path,)) threads.append(thread) thread.start() # 控制并发数 if len(threads) >= self.max_workers: for t in threads: t.join() threads = [] # 等待剩余线程完成 for thread in threads: thread.join() return results8. 总结
8.1 重点回顾
通过本教程,我们学习了SenseVoice-Small语音识别模型输出后处理的完整流程:
- 理解原始输出结构:掌握了模型输出的JSON格式和各种标签含义
- 基础清洗方法:学会了提取纯净文本和格式化事件信息
- 高级处理技巧:了解了多语言处理和基于时间戳的分段方法
- 完整管道构建:创建了可配置的后处理类,满足不同需求
- 实际集成应用:将后处理功能集成到Gradio界面中
8.2 下一步建议
想要进一步提升后处理效果,可以尝试:
- 自定义规则:根据你的具体业务场景,添加特定的文本处理规则
- 机器学习方法:使用序列标注模型来自动修正识别错误
- 实时处理优化:对于实时应用,优化处理管道以减少延迟
- 多模型集成:结合其他NLP模型进行更深层次的文本理解和处理
记住,好的后处理能让语音识别结果更加实用。根据你的具体需求选择合适的处理策略,平衡文本纯净度和信息保留程度。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。