欢迎光临
我们一直在努力

SenseVoice-small WebUI二次开发:添加自定义热词/行业术语识别

SenseVoice-small WebUI二次开发:添加自定义热词/行业术语识别

1. 为什么需要自定义热词识别?

如果你用过语音转文字工具,可能会遇到这样的尴尬:系统把“Transformer”识别成“变压器”,把“CUDA”识别成“酷达”,或者把你公司特有的产品名、内部术语识别得乱七八糟。在专业会议、技术讨论或特定行业应用中,这种错误会严重影响信息的准确性和可用性。

SenseVoice-small 本身是一个强大的轻量级多语言语音识别模型,支持50多种语言,但在处理特定领域的专有名词、行业术语或人名时,它和所有通用模型一样,会遇到“知识盲区”。这就是我们今天要解决的问题——通过二次开发,为SenseVoice-small WebUI添加自定义热词识别功能。

简单来说,我们要教会这个语音识别系统认识“它没见过但对你很重要”的词。

这个功能有什么用?

  • 提升专业场景准确率:让模型在医疗、金融、科技、法律等专业领域识别得更准。
  • 个性化定制:根据你的业务需求,添加公司产品名、员工姓名、内部代码等。
  • 改善用户体验:减少后期手动修改识别结果的工作量,提升效率。

2. 理解SenseVoice-small的工作原理

在动手修改之前,我们先花几分钟了解一下SenseVoice-small是怎么工作的。这能帮你更好地理解我们要修改哪里,以及为什么这么改。

SenseVoice-small是一个端到端的语音识别模型,它的工作流程可以简化为三步:

  • 音频输入:接收音频文件或实时录音。
  • 特征提取与识别:将音频转换成模型能理解的数字特征,然后通过神经网络预测出最可能的文字序列。
  • 后处理与输出:对识别出的原始文字进行一些整理(比如标点恢复、数字标准化),然后呈现给用户。
  • 我们要修改的,主要是第2步和第3步之间的环节。模型识别出的是“音素”或“子词”序列,我们需要在这个阶段,引导模型更倾向于输出我们预先定义好的“热词”。

    技术实现思路: 传统的热词增强通常在解码阶段进行。我们可以在模型的“波束搜索”解码过程中,为出现在热词列表中的词元(token)增加一个“奖励分数”(bonus score)。这样,当解码器在多个候选路径中做选择时,包含热词的路径会获得更高的分数,从而更有可能被选为最终输出。

    对于SenseVoice-small的ONNX版本,我们可以通过修改其Python推理脚本,在调用ONNX Runtime进行解码时,融入一个简单的热词权重机制。

    3. 开发环境与代码准备

    3.1 找到关键文件

    首先,你需要登录到部署了SenseVoice-small WebUI的服务器。根据提供的资料,项目路径通常在 /root/sensevoice-small-语音识别-onnx。

    我们需要找到WebUI的后端处理代码。它很可能在类似下面的位置:

    cd /root/sensevoice-small-语音识别-onnx
    find . -name "*.py" | grep -E "(app|server|api|infer|recog)" | head -10

    通常,主应用文件可能是 app.py, webui.py, 或 server.py。推理核心代码可能在 inference.py, recognizer.py 或 model.py 中。

    假设我们找到了核心推理文件 recognizer.py。在修改前,务必备份原文件:

    cp recognizer.py recognizer.py.backup

    3.2 创建热词配置文件

    我们在项目根目录创建一个简单的热词配置文件 hotwords.txt,格式可以是一行一个词,或者“热词:权重”的格式。权重是个小数,比如1.5,表示给这个词的得分加成50%。

    hotwords.txt 示例:

    # 格式:热词 或 热词:权重
    Transformer
    CUDA:1.3
    PyTorch
    ONNXRuntime:1.2
    张医生
    心肌梗死:2.0
    # 公司内部术语
    ProjectAlpha
    Q4财报

    权重越高,模型在解码时越倾向于输出该词。对于非常重要的术语(如医疗诊断名),可以设置较高的权重(如2.0)。一般术语设为1.2-1.5即可。

    4. 修改推理代码添加热词功能

    现在,我们开始修改 recognizer.py(或你找到的对应文件)。以下是修改的核心步骤。

    4.1 在类初始化时加载热词

    首先,在模型的初始化方法 __init__ 中,添加加载热词配置的代码。

    import os

    class SenseVoiceRecognizer:
    def __init__(self, model_path, …其他参数…):
    # … 原有的初始化代码 …
    self.model_path = model_path
    # … 加载ONNX模型等操作 …

    # ===== 新增:热词加载 =====
    self.hotwords = {}
    self.hotword_boost = 0.0 # 全局热词奖励分数系数,可配置
    self._load_hotwords()

    def _load_hotwords(self, hotword_file='hotwords.txt'):
    """
    从文件加载热词及其权重。
    如果文件不存在,则热词列表为空。
    """
    self.hotwords.clear()
    if not os.path.exists(hotword_file):
    print(f"[INFO] 热词文件 {hotword_file} 不存在,跳过加载。")
    return

    try:
    with open(hotword_file, 'r', encoding='utf-8') as f:
    for line in f:
    line = line.strip()
    if not line or line.startswith('#'):
    continue
    # 支持“热词:权重”和“热词”两种格式
    if ':' in line:
    word, weight = line.split(':', 1)
    word = word.strip()
    try:
    weight = float(weight.strip())
    except ValueError:
    weight = 1.5 # 默认权重
    else:
    word = line
    weight = 1.5 # 默认权重
    if word:
    self.hotwords[word] = weight
    print(f"[INFO] 已加载 {len(self.hotwords)} 个热词。")
    except Exception as e:
    print(f"[WARNING] 加载热词文件失败: {e}")

    4.2 修改解码过程(关键步骤)

    这是最核心的部分。我们需要在模型输出logits(每个时间步上各个词元的得分)之后,解码成文本之前,对热词相关的词元进行加分。

    由于SenseVoice-small使用ONNX Runtime,我们可能需要干预其解码器(通常是CTC解码或Transducer解码)。这里提供一个概念性的修改方法。注意:实际代码取决于原解码器的具体实现。

    假设原有一个 _decode 方法,我们修改它:

    def _decode(self, logits, seq_len):
    """
    解码logits为文本,加入热词增强。
    logits: 模型输出的原始得分 [Batch, Time, Vocab]
    seq_len: 实际序列长度
    """
    # 假设 vocab_list 是词表ID到文字的映射列表,需要在__init__中加载
    # self.vocab_list = […]

    batch_size, time_steps, vocab_size = logits.shape
    decoded_texts = []

    for b in range(batch_size):
    # 获取当前样本的有效logits
    cur_logits = logits[b, :seq_len[b], :].copy() # 使用copy避免修改原数据

    # ==== 热词增强:遍历每个时间步 ====
    for t in range(seq_len[b]):
    # 获取当前时间步所有词元的得分
    time_step_logits = cur_logits[t, :]

    # 遍历热词字典
    for hotword, boost_weight in self.hotwords.items():
    # 关键:找到热词对应的词元ID。
    # 情况1:热词是单个词元(在词表中)。对于中文,一个字可能就是一个词元。
    # 情况2:热词由多个词元组成(如英文单词或中文词组)。
    # 这里简化处理:假设热词都能在词表中找到对应ID(对于中文单字热词可行)。
    # 更复杂的实现需要考虑子词(BPE)分词,这里不展开。

    # 假设我们有一个方法将词映射到词元ID(需要根据实际词表实现)
    token_ids = self._word_to_token_ids(hotword)
    if token_ids:
    # 简单增强:给这些词元ID的得分加上一个奖励分数
    # 奖励分数 = log(boost_weight) * 一个全局系数(如5.0)
    bonus = np.log(boost_weight) * 5.0
    for tid in token_ids:
    if tid < vocab_size:
    time_step_logits[tid] += bonus

    cur_logits[t, :] = time_step_logits # 写回增强后的logits

    # ==== 使用增强后的logits进行解码 ====
    # 调用原有的解码逻辑,例如CTC贪婪解码或波束搜索
    # 假设原有方法叫 _ctc_greedy_decode
    tokens = self._ctc_greedy_decode(cur_logits)
    # 将词元ID序列转换成文字
    text = self._ids_to_text(tokens)
    decoded_texts.append(text)

    return decoded_texts

    def _word_to_token_ids(self, word):
    """
    将词转换成词元ID列表。
    这是一个简化示例。实际中,SenseVoice可能使用BPE或字词混合词表。
    你需要根据模型的真实词表文件来实现此方法。
    """
    # 伪代码:如果词表是字符级,直接查找每个字符的ID
    token_ids = []
    for char in word:
    if char in self.char_to_id: # 需要预先构建 char_to_id 映射
    token_ids.append(self.char_to_id[char])
    return token_ids

    重要说明:上面的 _word_to_token_ids 方法是最大的简化。SenseVoice-small 很可能使用 SentencePiece 或 BPE 子词词表。一个更实用的方法是:在加载热词时,就使用模型的分词器(Tokenizer)将每个热词预先转换成词元ID序列并存储起来。在解码时直接使用这些ID序列进行增强。

    4.3 修改WebUI API以接收热词

    为了让用户能在界面上动态管理热词,我们需要修改WebUI的后端API。

    找到处理识别请求的API函数(可能在 app.py 中),它通常接收音频文件或数据。我们修改它,使其能接收一个可选的“热词列表”参数。

    from flask import request, jsonify

    @app.route('/api/recognize', methods=['POST'])
    def recognize_audio():
    # … 原有的获取音频文件、语言的代码 …

    # ===== 新增:获取热词参数 =====
    hotwords_list = request.form.get('hotwords', '')
    # hotwords_list 可以是逗号分隔的字符串,如 "Transformer,CUDA,PyTorch"

    # 调用识别器,传入热词
    # 我们需要修改 recognizer 的识别方法,使其能接收临时热词
    try:
    # 假设 recognizer 有一个 transcribe 方法,我们为其添加 hotwords 参数
    result = recognizer.transcribe(
    audio_data=audio_data,
    language=language,
    hotwords=hotwords_list # 传入热词
    )
    return jsonify({'text': result['text'], 'language': result['lang']})
    except Exception as e:
    return jsonify({'error': str(e)}), 500

    同时,需要修改 SenseVoiceRecognizer 类的 transcribe 方法,使其能接收并合并临时热词与文件热词。

    def transcribe(self, audio_data, language='auto', hotwords=''):
    """
    执行语音识别。
    audio_data: 音频数据
    language: 语言代码
    hotwords: 逗号分隔的热词字符串,用于本次识别
    """
    # 合并文件热词和本次传入的热词
    current_hotwords = self.hotwords.copy()
    if hotwords:
    for word in hotwords.split(','):
    word = word.strip()
    if word:
    current_hotwords[word] = current_hotwords.get(word, 1.5) # 默认权重1.5

    # 将 current_hotwords 传递给内部解码函数
    # 这需要修改内部解码逻辑,使其能接收一个 hotwords_dict 参数
    # … 音频预处理、特征提取 …
    # logits = self.model_inference(features)
    # text = self._decode_with_hotwords(logits, seq_len, current_hotwords)
    # …
    return {'text': text, 'lang': detected_lang}

    5. 前端界面改造

    现在后端功能已经添加,我们需要在WebUI界面上给用户一个输入热词的地方。

    找到WebUI的HTML或模板文件(可能是 templates/index.html 或前端JavaScript文件)。在语言选择区域附近,添加一个输入框。

    <!– 在语言选择区域下方添加 –>
    <div class="form-group">
    <label for="hotwords-input">🔤 自定义热词(可选,用逗号分隔)</label>
    <input type="text" id="hotwords-input" class="form-control"
    placeholder="例如:Transformer, CUDA, 张医生, 心肌梗死"
    title="在此输入本次识别需要加强的词汇,多个词用英文逗号隔开。">
    <small class="form-text text-muted">输入专业术语或人名,可提升识别准确率。</small>
    </div>

    然后,修改JavaScript中发起识别请求的代码,将热词输入框的值传递给后端。

    // 找到发起识别请求的函数(例如名为 startRecognition)
    function startRecognition() {
    const audioFile = …; // 获取音频文件
    const language = …; // 获取选择的语言
    const hotwords = document.getElementById('hotwords-input').value; // 获取热词

    const formData = new FormData();
    formData.append('audio', audioFile);
    formData.append('language', language);
    formData.append('hotwords', hotwords); // 添加热词参数

    fetch('/api/recognize', {
    method: 'POST',
    body: formData
    })
    .then(response => response.json())
    .then(data => {
    // 显示结果
    document.getElementById('result-text').innerText = data.text;
    })
    .catch(error => {
    console.error('识别失败:', error);
    });
    }

    6. 测试与验证

    修改完成后,重启WebUI服务以使更改生效。

    supervisorctl restart sensevoice:sensevoice-webui

    测试步骤:

  • 准备测试音频:录制或准备一段包含你自定义热词的音频。例如,说:“我们在项目中使用Transformer模型和CUDA加速。”
  • 不使用热词:在WebUI中上传音频,选择语言,不填写热词,点击识别。观察结果,可能识别为“我们在项目中使用变压器模型和酷达加速。”
  • 使用热词:在“自定义热词”输入框中填写 Transformer, CUDA,再次识别。
  • 对比结果:理想情况下,第二次识别结果应正确输出“Transformer”和“CUDA”。
  • 调试技巧:

    • 如果功能不生效,首先检查后端日志:tail -f /root/sensevoice-small-语音识别-onnx/logs/webui.log
    • 查看热词是否被正确加载和传递。
    • 检查解码部分的代码逻辑,确认奖励分数被正确添加到logits上。
    • 对于多字热词(如“心肌梗死”),确保你的 _word_to_token_ids 方法能正确将其分解为词元ID序列。

    7. 总结与进阶思路

    通过以上步骤,我们成功为SenseVoice-small WebUI添加了基础的自定义热词识别功能。这个实现虽然简化,但涵盖了核心思路:在解码阶段,通过给特定词元的得分“加分”,来引导模型输出我们想要的词汇。

    回顾一下我们做了什么:

  • 理解需求与原理:明确了热词功能的价值和实现思路。
  • 定位与备份:找到了项目中的关键代码文件并做好备份。
  • 数据准备:创建了热词配置文件。
  • 后端增强:修改了模型识别器的初始化、热词加载和解码逻辑。
  • API扩展:修改了Web后端API,使其能接收前端传来的热词。
  • 前端改造:在用户界面上添加了热词输入框。
  • 测试验证:通过对比测试验证功能是否生效。
  • 进阶优化方向:

    • 热词权重动态调整:可以根据热词在音频中出现的声学置信度动态微调奖励分数。
    • 热词文件管理Web界面:开发一个简单的界面,让用户可以在WebUI上直接添加、删除、修改 hotwords.txt 文件中的内容,而无需登录服务器。
    • 热词组与场景:支持为不同的识别场景(如“医疗会议”、“技术研讨”)配置不同的热词组,并在界面上切换。
    • 集成更复杂的解码器:如果你对性能有更高要求,可以集成开源的、支持热词增强的先进解码器,如 flashlight 或 espnet 中的相关组件。

    这个二次开发案例展示了如何将一个通用的AI工具进行定制化,使其更好地服务于特定领域和场景。希望这个指南能帮助你打造一个更精准、更贴心的语音识别服务。


    获取更多AI镜像

    想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。

    赞(0)
    未经允许不得转载:171主机测评 » SenseVoice-small WebUI二次开发:添加自定义热词/行业术语识别
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址