构建自己的语音助手

news2024/9/28 3:30:21

在我最近发布关于如何构建自己的 RAG 并在本地运行它的帖子之后。今天,我们更进一步,不仅实现了大型语言模型的对话能力,还增加了听力和口语能力。这个想法很简单:我们将创建一个语音助手,让人想起标志性钢铁侠电影中的贾维斯或星期五,它可以在你的计算机上离线运行。

由于这是一个入门教程,我将用 Python 实现它,并使其足够简单,适合初学者。最后,我将提供一些关于如何扩展应用程序的指导。

完整代码可在github找到。

 NSDT工具推荐: Three.js AI纹理开发包 - YOLO合成数据生成器 - GLTF/GLB在线编辑 - 3D模型格式在线转换 - 可编程3D场景编辑器 - REVIT导出3D模型插件 - 3D模型语义搜索引擎 - AI模型在线查看 - Three.js虚拟轴心开发包 - 3D模型在线减面 - STL模型在线切割

1、Techstack

首先,设置一个虚拟 Python 环境。我们有多种选择,包括 pyenv、virtualenv、poetry 和其他具有类似用途的库。就我个人而言,由于个人喜好,我将在本教程中使用 Poetry。

以下是需要安装的几个关键库:

  • rich:用于视觉上吸引人的控制台输出。
  • openai-whisper:用于语音到文本转换的强大工具。
  • suno-bark:用于文本到语音合成的尖端库,确保高质量的音频输出。
  • langchain:用于与大型语言模型 (LLM) 交互的简单库。
  • sounddevice、pyaudio 和 Speechrecognition:用于音频录制和播放的必备工具。

有关依赖项的详细列表,请参阅此处的链接。

这里最关键的组件是大型语言模型 (LLM) 后端,我们将使用 Ollama。Ollama 被广泛认为是一种流行的离线运行和服务 LLM 的工具。基本上,你只需下载 Ollama 应用程序,提取喜欢的模型,然后运行它即可。

2、架构

好的,如果一切设置完毕,让我们继续下一步。以下是我们应用程序的总体架构,它基本上包含 3 个主要组件:

  • 语音识别:利用 OpenAI 的 Whisper,我们将口语转换为文本。Whisper 对各种数据集的训练确保了它能够熟练处理各种语言和方言。
  • 对话链:对于对话功能,我们将使用 Llama-2 模型的 Langchain 接口,该模型使用 Ollama 提供服务。此设置可确保无缝且引人入胜的对话流程。
  • 语音合成器:文本到语音的转换是通过 Bark 实现的,Bark 是 Suno AI 最先进的模型,以其逼真的语音制作而闻名。

工作流程很简单:录制语音、转录为文本、使用 LLM 生成响应,并使用 Bark 发出响应的声音。

2、实现

实施首先要基于 Bark 制作 TextToSpeechService,结合从文本合成语音的方法以及无缝处理较长文本输入的方法,如下所示:

import nltk
import torch
import warnings
import numpy as np
from transformers import AutoProcessor, BarkModel

warnings.filterwarnings(
    "ignore",
    message="torch.nn.utils.weight_norm is deprecated in favor of torch.nn.utils.parametrizations.weight_norm.",
)


class TextToSpeechService:
    def __init__(self, device: str = "cuda" if torch.cuda.is_available() else "cpu"):
        """
        Initializes the TextToSpeechService class.
        Args:
            device (str, optional): The device to be used for the model, either "cuda" if a GPU is available or "cpu".
            Defaults to "cuda" if available, otherwise "cpu".
        """
        self.device = device
        self.processor = AutoProcessor.from_pretrained("suno/bark-small")
        self.model = BarkModel.from_pretrained("suno/bark-small")
        self.model.to(self.device)

    def synthesize(self, text: str, voice_preset: str = "v2/en_speaker_1"):
        """
        Synthesizes audio from the given text using the specified voice preset.
        Args:
            text (str): The input text to be synthesized.
            voice_preset (str, optional): The voice preset to be used for the synthesis. Defaults to "v2/en_speaker_1".
        Returns:
            tuple: A tuple containing the sample rate and the generated audio array.
        """
        inputs = self.processor(text, voice_preset=voice_preset, return_tensors="pt")
        inputs = {k: v.to(self.device) for k, v in inputs.items()}

        with torch.no_grad():
            audio_array = self.model.generate(**inputs, pad_token_id=10000)

        audio_array = audio_array.cpu().numpy().squeeze()
        sample_rate = self.model.generation_config.sample_rate
        return sample_rate, audio_array

    def long_form_synthesize(self, text: str, voice_preset: str = "v2/en_speaker_1"):
        """
        Synthesizes audio from the given long-form text using the specified voice preset.
        Args:
            text (str): The input text to be synthesized.
            voice_preset (str, optional): The voice preset to be used for the synthesis. Defaults to "v2/en_speaker_1".
        Returns:
            tuple: A tuple containing the sample rate and the generated audio array.
        """
        pieces = []
        sentences = nltk.sent_tokenize(text)
        silence = np.zeros(int(0.25 * self.model.generation_config.sample_rate))

        for sent in sentences:
            sample_rate, audio_array = self.synthesize(sent, voice_preset)
            pieces += [audio_array, silence.copy()]

        return self.model.generation_config.sample_rate, np.concatenate(pieces)
  • 初始化 ( __init__):该类采用可选的设备参数,该参数指定要用于模型的设备(如果有 GPU,则为 cuda,否则为 cpu)。它从 suno/bark-small 预训练模型加载 Bark 模型和相应的处理器。您也可以通过为模型加载器指定 suno/bark 来使用大型版本。
  • 合成 ( synthesize):此方法采用文本输入和 voice_preset 参数,该参数指定要用于合成的语音。可以在此处查看其他 voice_preset 值。它使用处理器准备输入文本和语音预设,然后使用 model.generate() 方法生成音频数组。生成的音频数组将转换为 NumPy 数组,并随音频数组一起返回采样率。
  • 长格式合成 (long_form_synthesize):此方法用于合成较长的文本输入。它首先使用 nltk.sent_tokenize 函数将输入文本标记为句子。对于每个句子,它都会调用 synthesize 方法来生成音频数组。然后,它将生成的音频数组连接起来,并在每个句子之间添加短暂的静音(0.25 秒)。

现在我们已经设置了 TextToSpeechService,我们需要为大型语言模型 (LLM) 服务准备 Ollama 服务器。为此,我们需要遵循以下步骤:

  • 提取最新的 Llama-2 模型:运行以下命令从 Ollama 存储库下载最新的 Llama-2 模型: ollama pull llama2
  • 启动 Ollama 服务器:如果服务器尚未启动,请执行以下命令来启动它: ollama serve

完成这些步骤后,你的应用程序将能够使用 Ollama 服务器和 Llama-2 模型来生成对用户输入的响应。

接下来,我们将转到主要应用程序逻辑。首先,我们需要初始化以下组件:

  • Rich Console:我们将使用 Rich 库在终端内为用户创建更好的交互式控制台。
  • Whisper Speech-to-Text:我们将初始化 Whisper 语音识别模型,这是 OpenAI 开发的最先进的开源语音识别系统。我们将使用基本英语模型 ( base.en) 转录用户输入。
  • Bark Text-to-Speech:我们将初始化上面实现的 Bark Text-to-Speech 合成器实例。
  • 对话链:我们将使用 Langchain 库中的内置 ConversationalChain,它提供了管理对话流的模板。我们将配置它以使用带有 Ollama 后端的 Llama-2 语言模型。
import time
import threading
import numpy as np
import whisper
import sounddevice as sd
from queue import Queue
from rich.console import Console
from langchain.memory import ConversationBufferMemory
from langchain.chains import ConversationChain
from langchain.prompts import PromptTemplate
from langchain_community.llms import Ollama
from tts import TextToSpeechService

console = Console()
stt = whisper.load_model("base.en")
tts = TextToSpeechService()

template = """
You are a helpful and friendly AI assistant. You are polite, respectful, and aim to provide concise responses of less 
than 20 words.
The conversation transcript is as follows:
{history}
And here is the user's follow-up: {input}
Your response:
"""
PROMPT = PromptTemplate(input_variables=["history", "input"], template=template)
chain = ConversationChain(
    prompt=PROMPT,
    verbose=False,
    memory=ConversationBufferMemory(ai_prefix="Assistant:"),
    llm=Ollama(),
)

现在,让我们定义必要的函数:

  • record_audio:此函数在单独的线程中运行,使用 sounddevice.RawInputStream 从用户的麦克风捕获音频数据。每当有新的音频数据可用时,就会调用回调函数,并将数据放入 data_queue 中以供进一步处理。
  • transcribe:此函数利用 Whisper 实例将 data_queue 中的音频数据转录为文本。
  • get_llm_response:此函数将当前对话上下文提供给 Llama-2 语言模型(通过 Langchain的 ConversationalChain)并检索生成的文本响应。
  • play_audio:此函数获取 Bark 文本转语音引擎生成的音频波形,并使用声音播放库(例如 sounddevice)将其播放给用户。
def record_audio(stop_event, data_queue):
    """
    Captures audio data from the user's microphone and adds it to a queue for further processing.
    Args:
        stop_event (threading.Event): An event that, when set, signals the function to stop recording.
        data_queue (queue.Queue): A queue to which the recorded audio data will be added.
    Returns:
        None
    """
    def callback(indata, frames, time, status):
        if status:
            console.print(status)
        data_queue.put(bytes(indata))

    with sd.RawInputStream(
        samplerate=16000, dtype="int16", channels=1, callback=callback
    ):
        while not stop_event.is_set():
            time.sleep(0.1)

def transcribe(audio_np: np.ndarray) -> str:
    """
    Transcribes the given audio data using the Whisper speech recognition model.
    Args:
        audio_np (numpy.ndarray): The audio data to be transcribed.
    Returns:
        str: The transcribed text.
    """
    result = stt.transcribe(audio_np, fp16=False)  # Set fp16=True if using a GPU
    text = result["text"].strip()
    return text

def get_llm_response(text: str) -> str:
    """
    Generates a response to the given text using the Llama-2 language model.
    Args:
        text (str): The input text to be processed.
    Returns:
        str: The generated response.
    """
    response = chain.predict(input=text)
    if response.startswith("Assistant:"):
        response = response[len("Assistant:") :].strip()
    return response

def play_audio(sample_rate, audio_array):
    """
    Plays the given audio data using the sounddevice library.
    Args:
        sample_rate (int): The sample rate of the audio data.
        audio_array (numpy.ndarray): The audio data to be played.
    Returns:
        None
    """
    sd.play(audio_array, sample_rate)
    sd.wait()

然后,我们定义主应用程序循环。主应用程序循环引导用户完成对话交互,如下所示:

  • 提示用户按 Enter 键开始录制他们的输入。
  • 一旦用户按下 Enter 键,就会在单独的线程中调用 record_audio 函数来捕获用户的音频输入。
  • 当用户再次按下 Enter 键停止录制时,将使用 transcribe 函数转录音频数据。
  • 然后将转录的文本传递给 get_llm_response 函数,该函数使用 Llama-2 语言模型生成响应。
  • 生成的响应将打印到控制台并使用 play_audio 函数播放给用户。
if __name__ == "__main__":
    console.print("[cyan]Assistant started! Press Ctrl+C to exit.")

    try:
        while True:
            console.input(
                "Press Enter to start recording, then press Enter again to stop."
            )

            data_queue = Queue()  # type: ignore[var-annotated]
            stop_event = threading.Event()
            recording_thread = threading.Thread(
                target=record_audio,
                args=(stop_event, data_queue),
            )
            recording_thread.start()

            input()
            stop_event.set()
            recording_thread.join()

            audio_data = b"".join(list(data_queue.queue))
            audio_np = (
                np.frombuffer(audio_data, dtype=np.int16).astype(np.float32) / 32768.0
            )

            if audio_np.size > 0:
                with console.status("Transcribing...", spinner="earth"):
                    text = transcribe(audio_np)
                console.print(f"[yellow]You: {text}")

                with console.status("Generating response...", spinner="earth"):
                    response = get_llm_response(text)
                    sample_rate, audio_array = tts.long_form_synthesize(response)

                console.print(f"[cyan]Assistant: {response}")
                play_audio(sample_rate, audio_array)
            else:
                console.print(
                    "[red]No audio recorded. Please ensure your microphone is working."
                )

    except KeyboardInterrupt:
        console.print("\n[red]Exiting...")

    console.print("[blue]Session ended.")

3、结果

一旦所有东西都放在一起,我们就可以运行应用程序,如这个视频所示。由于 Bark 模型很大,即使是较小版本,应用程序在我的 MacBook 上运行也相当慢。因此,我稍微加快了视频的速度。对于那些拥有支持 CUDA 的计算机的人来说,它可能会运行得更快。以下是我们应用程序的主要功能:

  • 基于语音的交互:用户可以开始和停止录制他们的语音输入,助手通过播放生成的音频来响应。
  • 对话上下文:助手保持对话的上下文,从而实现更连贯和相关的响应。使用 Llama-2 语言模型允许助手提供简洁而有针对性的响应。

对于那些旨在将此应用程序提升到生产就绪状态的人,建议进行以下增强:

  • 性能优化:结合模型的优化版本,例如 whisper.cpp、llama.cpp 和 bark.cpp,这些版本旨在提高性能,尤其是在低端计算机上。
  • 可定制的机器人提示:实现一个系统,允许用户自定义机器人的角色和提示,从而创建不同类型的助手(例如,个人、专业或特定领域)。
  • 图形用户界面 (GUI):开发一个用户友好的 GUI 来增强整体用户体验,使应用程序更易于访问且更具视觉吸引力。
  • 多模式功能:扩展应用程序以支持多模式交互,例如除了基于语音的响应之外,还能够生成和显示图像、图表或其他视觉内容。

最后,我们完成了简单的语音助手应用程序。语音识别、语言建模和文本转语音技术的结合展示了我们如何构建听起来很难但实际上可以在你的计算机上运行的东西。


原文链接:构建自己的语音助手 - BimAnt

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2052645.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于asp.net的办公协同管理系统源码分享

今天给大家分享一个asp.net开发的webform框架的办公协同管理系统源码SQLserver数据库 1.主要功能 这个项目是帮助一个学生指导的毕业设计,包含用户登陆、用户管理、车辆 管理、文件管理、个人中心、后台管理、文件上传、人事管理、系统日志等 等模块。2.开发工具及…

【Django-vue-admin学习笔记】页面自动计算日期差额的方法

在许多应用场景中,尤其是在管理系统中,经常需要对日期进行动态计算和展示,以帮助用户了解关键日期的即时状态。例如,在学生宿舍管理系统中,显示学生的退宿倒计时可以帮助管理人员有效监控即将到期的宿舍安排,并及时进行必要的调整。这样的功能不仅提高了管理效率,也增加…

H5接入企微JS-SDK,使用wx.previewFile进行文件预览

最近上项目,需求是做一个附件预览并且可以进行保存到手机、用其他应用打开的需求的需求,用企微的JS-SDK的wx.previewFile就可以满足以上的需求了 详细的可以参考:企业微信官方文档 前端 1、在项目的index.html中添加:jweixin-1.2…

两种用MATLAB绘制色块的方法

绘制色块首先可以想到用填充像素的方式 % 定义图像的尺寸 imageSize 500;% 创建一个 imageSize x imageSize x 3 的矩阵,每个像素都是绿色 % RGB颜色模型中绿色的值为 [0, 1, 0] greenImage zeros(imageSize, imageSize, 3); greenImage(:, :, 2) ones(imageSiz…

Kubeadm快速安装 Kubernetes集群

Kubernetes的基础概念 Kubernetes(通常简称为K8s)是一个开源的容器编排系统,用于自动化部署、扩展和管理容器化应用程序。Kubernetes 提供了强大的抽象能力,使得开发者能够专注于应用程序的逻辑,而无需担心底层容器的…

某框架路由渗透

前言 某天在互联网平台上看待DWR路由的文章,然后去搜索了一下相关的信息,发现该DWR路由技术挺久远的,因此就简单的学习了一下该路由,然后发现该DWR路由也可能存在漏洞点,因此找了某站进行测试看看是否有无啥收获&…

学习笔记 韩顺平 零基础30天学会Java(2024.8.15)

P512 ArrayList底层源码2 P513 Vector注意事项 最近有点懈怠,要去新的环境上学了,有点焦虑,调整状态ing,准备开始研一,希望能继续本科的荣耀!! PS:本科应该算是荣耀的吧哈哈哈哈哈

高性价比运动耳机都有哪些?五大高性价比运动耳机推荐

对于很多人来说,大家可能会选择听音乐来放松身心,打发掉无聊的时间。开放式耳机对比入耳式耳机的优势就是既能听到耳机内的声音又能感知环境音,很适合在户外以及办公时使用。像我每天坐地铁上下班的时候都会习惯戴耳机,但以前戴入…

微信视频号评论采集秘籍:三招让你迅速收集用户反馈

在短视频盛行的今天,微信视频号以其庞大的用户基数成为品牌与用户互动的重要阵地。但如何从海量评论中迅速收集有价值的用户反馈,成为众多内容创作者与营销人员的难题。本文将揭秘三招实用技巧,助你高效采集微信视频号评论,精准把…

使用HTML和cgi控制I.MX6ULL开发板上的LED

一.HTML文件 <!DOCTYPE html> <html><head><meta charset"utf-8"><title>LED_device</title> </head><body><form action"/cgi-bin/led.cgi" method"post"><p>LED设备号</…

夫妻双方均年过四十长期分居,离婚不仅因为不同房!李秘书专业写作:这是一篇涉离婚纠纷的民事起诉状

夫妻双方均年过四十长期分居&#xff0c;离婚不仅因为不同房&#xff01; 李秘书专业写作&#xff1a;这是一篇涉离婚纠纷的民事起诉状 &#xff08;精品范文&#xff09; 离 婚 起 诉 状 原告&#xff1a;曹某琴&#xff0c;女&#xff0c;现年40岁&#xff0c;汉族&#x…

Web前端 - HTML、CSS

w3school 在线教程 Vscode工具&#xff1a; Live Preview: 右键show Preview 实时预览效果 Live Preview: HTML AI助手 CSS (Cascading Style Sheets&#xff0c;层叠样式表&#xff09;&#xff0c;是一种用来为结构化文档&#xff08;如 HTML 文档或 XML 应用&#xff0…

PHP校园跑腿跳蚤市场助手系统小程序源码

&#x1f3c3;‍♀️&#x1f6cd;️【校园新风尚】跑腿跳蚤市场助手系统&#xff0c;便捷交易新体验&#xff01;&#x1f389; &#x1f680;一键下单&#xff0c;跑腿无忧 学习太忙没时间取快递&#xff1f;食堂太远懒得动&#xff1f;校园跑腿助手系统来帮你&#xff01;…

分子筛自动填充高原制氧机的特点

在高原地区&#xff0c;氧气稀薄&#xff0c;对人体的正常生理活动带来了诸多挑战。而分子筛自动填充高原制氧机的出现&#xff0c;为解决高原缺氧问题提供了有效的解决方案。以下是这种制氧机的一些特点&#xff1a; 高效制氧能力&#xff1a; 分子筛自动填充高原制氧机采用先…

PS DRAM接口的函数式编程模型(二)

DRAM Input Impedance (ODT) Calibration&#xff08;DRAM输入阻抗校准&#xff09; ODT技术通过在DRAM内部集成终结电阻&#xff0c;实现对信号线的终端匹配。当DRAM作为接收器时&#xff0c;ODT电阻能够吸收信号线上的反射波&#xff0c;防止信号反射对后续信号造成影响&…

职业院校云计算实训室建设方案全景剖析

在信息化社会的今天&#xff0c;云计算作为一项关键技术&#xff0c;正在迅速改变着教育和培训的方式。本文旨在探讨如何通过"职业院校云计算实训室建设方案"&#xff0c;为学生提供一个现代化、高效的学习和研究环境&#xff0c;以适应云计算技术的发展和市场需求。…

cpp depends源码有向图分析工具

使用 cpp-dependencies.exe --dir ./example --output image.dot 输出 参考 GitHub - tomtom-international/cpp-dependencies: Tool to check C #include dependencies (dependency graphs created in .dot format) 创作不易&#xff0c;小小的支持一下吧&#xff01;

06-图3 六度空间(C)

这个很好想&#xff0c;尤其是经过图的连通集&#xff0c;所以这一次我才有之前写的代码为主体以邻接表的方法构建了方法一&#xff0c;至于运用 邻接矩阵&#xff0c;可以查看我之前的图的连通集这一方案&#xff0c;稍微改装&#xff0c;便解决这一问题了。 “六度空间”理论…

【数据结构】PTA 带头结点的链式表操作集 C语言

本题要求实现带头结点的链式表操作集。 函数接口定义&#xff1a; List MakeEmpty(); Position Find( List L, ElementType X ); bool Insert( List L, ElementType X, Position P ); bool Delete( List L, Position P ); 其中List结构定义如下&#xff1a; typedef struc…

Prism框架

使用 NuGet包下载 -- > Prism.Unity 框架中的数据与行为 BindableBase 在ViewModel中需要继承此类 通知属性的三种方式&#xff1a; public class MainViewModel : BindableBase {// 基本的通知属性private string _value;public string Value{get { return _value; }s…