音频课程文档

创建语音助手

Hugging Face's logo
加入 Hugging Face 社区

并获得增强的文档体验

开始使用

创建语音助手

在本节中,我们将整合三个我们已经有动手经验的模型来构建一个端到端的语音助手,名为 Marvin 🤖。与亚马逊的 Alexa 或苹果的 Siri 类似,Marvin 是一个虚拟语音助手,它响应特定的“唤醒词”,然后监听语音查询,最后用语音回答。

我们可以将语音助手流程分解为四个阶段,每个阶段都需要一个独立的模型。

1. 唤醒词检测

语音助手持续监听设备麦克风的音频输入,但只有当特定的“唤醒词”或“触发词”被说出时,它们才会启动。

唤醒词检测任务由一个小型设备端音频分类模型处理,该模型比语音识别模型小得多、轻得多,通常只有几百万个参数,而语音识别模型则有几亿个参数。因此,它可以在您的设备上持续运行而不会耗尽电池。只有当检测到唤醒词时,才会启动较大的语音识别模型,之后再将其关闭。

2. 语音转录

流程的下一个阶段是将语音查询转录为文本。在实践中,由于音频文件体积较大,将音频文件从本地设备传输到云端速度很慢,因此更有效的方法是直接在设备上使用自动语音识别 (ASR) 模型进行转录,而不是使用云端模型。设备端模型可能比云端托管的模型小,因此准确性较低,但更快的推理速度使其物有所值,因为我们可以近乎实时地运行语音识别,我们的语音话语在说话时就被转录。

我们现在对语音识别过程非常熟悉,所以这应该很简单!

3. 语言模型查询

既然我们知道了用户问了什么,我们现在需要生成一个回应!此任务的最佳候选模型是大型语言模型 (LLM),因为它们能够有效地理解文本查询的语义并生成合适的回应。

由于我们的文本查询很小(只有几个文本标记),而语言模型很大(几十亿个参数),运行 LLM 推理最有效的方式是将我们的文本查询从设备发送到云端运行的 LLM,生成文本回应,然后将回应返回到设备。

4. 合成语音

最后,我们将使用文本到语音 (TTS) 模型将文本回应合成为语音。这在设备上完成,但您也可以在云端运行 TTS 模型,生成音频输出并将其传输回设备。

我们已经做过几次了,所以这个过程会非常熟悉!

以下部分需要使用麦克风录制语音输入。由于 Google Colab 机器不兼容麦克风,建议您在本地运行此部分,无论是使用 CPU 还是 GPU(如果您有本地访问权限)。检查点大小已选择为足够小,以便在 CPU 上足够快地运行,因此即使没有 GPU,您仍然可以获得良好的性能。

唤醒词检测

语音助手流程的第一个阶段是检测唤醒词是否被说出,我们需要为此任务找到一个合适的预训练模型!您会记得在音频分类预训练模型部分中,Speech Commands 是一个语音单词数据集,旨在评估音频分类模型在 15 个以上简单命令词(如"up""down""yes""no")以及用于分类无语音的"silence"标签上的表现。花点时间通过 Hub 上的数据集查看器收听样本,重新熟悉 Speech Commands 数据集:数据集查看器

我们可以使用在 Speech Commands 数据集上预训练的音频分类模型,并从中选择一个简单的命令词作为我们选择的唤醒词。在 15 个以上可能的命令词中,如果模型以最高概率预测我们选择的唤醒词,我们就可以相当确定唤醒词已经被说出。

让我们前往 Hugging Face Hub,然后点击“模型”选项卡:https://huggingface.co/models

这将显示 Hugging Face Hub 上的所有模型,按过去 30 天的下载量排序

您会注意到左侧有一系列选项卡,我们可以选择这些选项卡来按任务、库、数据集等过滤模型。向下滚动并从音频任务列表中选择“音频分类”任务。

我们现在看到的是 Hub 上 500 多个音频分类模型的子集。为了进一步细化这个选择,我们可以按数据集过滤模型。点击“数据集”选项卡,然后在搜索框中输入“speech_commands”。当您开始输入时,您会看到speech_commands的选项出现在搜索选项卡下方。您可以点击此按钮来过滤所有音频分类模型,只显示那些在 Speech Commands 数据集上微调过的模型。

太棒了!我们看到有六个预训练模型可用于此特定数据集和任务(尽管如果您在以后阅读,可能会添加新模型!)。您会认出其中第一个模型是我们第 4 单元示例中使用的音频频谱图 Transformer 检查点。我们将再次使用此检查点来执行我们的唤醒词检测任务。

我们来用 `pipeline` 类加载检查点

from transformers import pipeline
import torch

device = "cuda:0" if torch.cuda.is_available() else "cpu"

classifier = pipeline(
    "audio-classification", model="MIT/ast-finetuned-speech-commands-v2", device=device
)

我们可以通过检查模型配置中的 id2label 属性来查看模型是根据哪些标签进行训练的。

classifier.model.config.id2label

好的!我们看到模型是在 35 个类别标签上训练的,包括我们上面描述的一些简单命令词,以及一些特定对象,如 "bed""house""cat"。我们看到这些类别标签中有一个名字:ID 27 对应于标签 “marvin”

classifier.model.config.id2label[27]
'marvin'

完美!我们可以用这个名字作为我们语音助手的唤醒词,类似于“Alexa”用于亚马逊的 Alexa,或“Hey Siri”用于苹果的 Siri。在所有可能的标签中,如果模型以最高的类别概率预测"marvin",我们就可以相当确定我们选择的唤醒词已经被说出。

现在我们需要定义一个函数,该函数持续监听设备的麦克风输入,并将音频连续传递给分类模型进行推理。为此,我们将使用 🤗 Transformers 附带的一个便捷辅助函数,名为 ffmpeg_microphone_live

此函数将指定长度 chunk_length_s 的小段音频转发给模型进行分类。为了确保音频块之间的平滑边界,我们以 chunk_length_s / 6 的步长在音频上运行一个滑动窗口。为了避免等待整个第一个音频块录制完毕才开始推理,我们还定义了一个最小临时音频输入长度 stream_chunk_s,它在达到 chunk_length_s 时间之前转发给模型。

函数 `ffmpeg_microphone_live` 返回一个**生成器**对象,它生成一系列音频块,每个音频块都可以传递给分类模型进行预测。我们可以将此生成器直接传递给 `pipeline`,`pipeline` 又会返回一系列输出预测,每个预测对应一个音频输入块。我们可以检查每个音频块的类别标签概率,并在检测到唤醒词被说出时停止唤醒词检测循环。

我们将使用一个非常简单的标准来判断唤醒词是否被说出:如果具有最高概率的类别标签是我们的唤醒词,并且此概率超过阈值 `prob_threshold`,则我们声明唤醒词已被说出。通过这种方式使用概率阈值来限制分类器,可以确保在音频输入是噪音时不会错误地预测唤醒词,因为在这种情况下,模型通常非常不确定,并且所有类别标签的概率都很低。您可能需要调整此概率阈值,或者通过基于(或不确定性)的度量来探索更复杂的唤醒词决策方法。

from transformers.pipelines.audio_utils import ffmpeg_microphone_live


def launch_fn(
    wake_word="marvin",
    prob_threshold=0.5,
    chunk_length_s=2.0,
    stream_chunk_s=0.25,
    debug=False,
):
    if wake_word not in classifier.model.config.label2id.keys():
        raise ValueError(
            f"Wake word {wake_word} not in set of valid class labels, pick a wake word in the set {classifier.model.config.label2id.keys()}."
        )

    sampling_rate = classifier.feature_extractor.sampling_rate

    mic = ffmpeg_microphone_live(
        sampling_rate=sampling_rate,
        chunk_length_s=chunk_length_s,
        stream_chunk_s=stream_chunk_s,
    )

    print("Listening for wake word...")
    for prediction in classifier(mic):
        prediction = prediction[0]
        if debug:
            print(prediction)
        if prediction["label"] == wake_word:
            if prediction["score"] > prob_threshold:
                return True

我们来试一下这个函数,看看它是如何工作的!我们将 `debug=True` 标志设置为打印出每个音频块的预测。让模型运行几秒钟,看看在没有语音输入时它会做出什么样的预测,然后清晰地说出唤醒词 `"marvin"`,并观察 `"marvin"` 的类别标签预测值飙升至接近 1。

launch_fn(debug=True)
Listening for wake word...
{'score': 0.055326107889413834, 'label': 'one'}
{'score': 0.05999856814742088, 'label': 'off'}
{'score': 0.1282748430967331, 'label': 'five'}
{'score': 0.07310110330581665, 'label': 'follow'}
{'score': 0.06634809821844101, 'label': 'follow'}
{'score': 0.05992642417550087, 'label': 'tree'}
{'score': 0.05992642417550087, 'label': 'tree'}
{'score': 0.999913215637207, 'label': 'marvin'}

太棒了!正如我们所料,模型在最初几秒钟内生成的是垃圾预测。没有语音输入,所以模型做出的预测接近随机,但概率非常低。一旦我们说出唤醒词,模型就会以接近 1 的概率预测 `"marvin"` 并终止循环,这表明唤醒词已被检测到,ASR 系统应该被激活!

语音转录

我们将再次使用 Whisper 模型作为我们的语音转录系统。具体来说,我们将加载Whisper Base English检查点,因为它足够小,可以提供良好的推理速度和合理的转录准确性。我们将使用一种技巧,通过巧妙地将音频输入转发到模型来获得接近实时的转录。和以前一样,您可以随意使用Hub上的任何语音识别检查点,包括 Wav2Vec2、MMS ASR 或其他 Whisper 检查点。

transcriber = pipeline(
    "automatic-speech-recognition", model="openai/whisper-base.en", device=device
)
如果您使用的是 GPU,可以增加检查点大小以使用Whisper Small English检查点,这将提供更好的转录准确性,并且仍在所需的延迟阈值内。只需将模型 ID 替换为:"openai/whisper-small.en"

现在我们可以定义一个函数来录制麦克风输入并转录相应的文本。借助 `ffmpeg_microphone_live` 辅助函数,我们可以控制语音识别模型的“实时性”。使用较小的 `stream_chunk_s` 有助于实现更实时的语音识别,因为我们将输入音频分成更小的块并实时转录。然而,这会牺牲较差的准确性,因为模型可供推断的上下文较少。

在转录语音时,我们还需要知道用户何时**停止**说话,以便我们可以终止录音。为了简单起见,我们将在第一个 `chunk_length_s`(默认为 5 秒)之后终止麦克风录音,但您可以尝试使用语音活动检测 (VAD) 模型来预测用户何时停止说话。

import sys


def transcribe(chunk_length_s=5.0, stream_chunk_s=1.0):
    sampling_rate = transcriber.feature_extractor.sampling_rate

    mic = ffmpeg_microphone_live(
        sampling_rate=sampling_rate,
        chunk_length_s=chunk_length_s,
        stream_chunk_s=stream_chunk_s,
    )

    print("Start speaking...")
    for item in transcriber(mic, generate_kwargs={"max_new_tokens": 128}):
        sys.stdout.write("\033[K")
        print(item["text"], end="\r")
        if not item["partial"][0]:
            break

    return item["text"]

我们来试试这个,看看效果如何!一旦麦克风开始工作,就开始说话,并观察您的转录以半实时的方式出现。

transcribe()
Start speaking...
 Hey, this is a test with the whisper model.

太棒了!您可以根据您的语速调整最大音频长度 `chunk_length_s`(如果您觉得说话时间不够,请增加它;如果您在最后等待,请减少它),以及实时因素 `stream_chunk_s`。只需将它们作为参数传递给 `transcribe` 函数即可。

语言模型查询

现在我们已经将语音查询转录完成,我们想要生成一个有意义的回应。为此,我们将使用托管在云端的 LLM。具体来说,我们将从 Hugging Face Hub 中选择一个 LLM,并使用推理 API 轻松查询模型。

首先,让我们前往 Hugging Face Hub。为了找到我们的 LLM,我们将使用🤗 Open LLM Leaderboard,这是一个根据四项生成任务的性能对 LLM 模型进行排名的 Space。我们将通过“instruct”进行搜索,以过滤掉经过指令微调的模型,因为这些模型应该更适合我们的查询任务。

我们将使用tiiuae/falcon-7b-instruct检查点,这是一个由TII开发的 7B 参数仅解码器语言模型,经过混合聊天和指令数据集的微调。您可以使用 Hugging Face Hub 上任何已启用“托管推理 API”的 LLM,只需查看模型卡右侧的小部件即可。

推理 API 允许我们将 HTTP 请求从本地机器发送到 Hub 上托管的 LLM,并以 `json` 文件的形式返回响应。我们只需提供 Hugging Face Hub 令牌(我们可以直接从 Hugging Face Hub 文件夹中检索)和我们希望查询的 LLM 的模型 ID。

from huggingface_hub import HfFolder
import requests


def query(text, model_id="tiiuae/falcon-7b-instruct"):
    api_url = f"https://api-inference.huggingface.co/models/{model_id}"
    headers = {"Authorization": f"Bearer {HfFolder().get_token()}"}
    payload = {"inputs": text}

    print(f"Querying...: {text}")
    response = requests.post(api_url, headers=headers, json=payload)
    return response.json()[0]["generated_text"][len(text) + 1 :]

我们来用一个测试输入试试看!

query("What does Hugging Face do?")
'Hugging Face is a company that provides natural language processing and machine learning tools for developers. They'

你会注意到使用推理 API 的推理速度非常快——我们只需从本地机器向托管模型发送少量文本标记,因此通信成本非常低。LLM 托管在 GPU 加速器上,因此推理运行速度非常快。最后,生成的响应会从模型传输回本地机器,同样通信开销也很低。

合成语音

现在我们已经准备好获得最终的语音输出!我们将再次使用 Microsoft SpeechT5 TTS 模型进行英语 TTS,但您可以使用任何您选择的 TTS 模型。让我们加载处理器和模型。

from transformers import SpeechT5Processor, SpeechT5ForTextToSpeech, SpeechT5HifiGan

processor = SpeechT5Processor.from_pretrained("microsoft/speecht5_tts")

model = SpeechT5ForTextToSpeech.from_pretrained("microsoft/speecht5_tts").to(device)
vocoder = SpeechT5HifiGan.from_pretrained("microsoft/speecht5_hifigan").to(device)

还有说话人嵌入

from datasets import load_dataset

embeddings_dataset = load_dataset("Matthijs/cmu-arctic-xvectors", split="validation")
speaker_embeddings = torch.tensor(embeddings_dataset[7306]["xvector"]).unsqueeze(0)

我们将重新使用我们在上一章语音到语音翻译中定义的 synthesise 函数。

def synthesise(text):
    inputs = processor(text=text, return_tensors="pt")
    speech = model.generate_speech(
        inputs["input_ids"].to(device), speaker_embeddings.to(device), vocoder=vocoder
    )
    return speech.cpu()

让我们快速验证一下它是否按预期工作。

from IPython.display import Audio

audio = synthesise(
    "Hugging Face is a company that provides natural language processing and machine learning tools for developers."
)

Audio(audio, rate=16000)

干得漂亮 👍

Marvin 🤖

现在我们已经为语音助手管道的四个阶段中的每个阶段都定义了一个函数,剩下的就是将它们组合在一起以获得我们的端到端语音助手。我们将简单地将四个阶段连接起来,从唤醒词检测 (`launch_fn`)、语音转录、查询 LLM,最后是语音合成。

launch_fn()
transcription = transcribe()
response = query(transcription)
audio = synthesise(response)

Audio(audio, rate=16000, autoplay=True)

用一些提示词试一试吧!以下是一些示例,供您入门:

  • 世界上最热的国家是哪个?
  • Transformer 模型是如何工作的?
  • 你会说西班牙语吗?

至此,我们的端到端语音助手就完成了,它是使用您在本课程中学习到的 🤗 音频工具,并加入了一些 LLM 的魔法。我们还可以对语音助手进行一些扩展以改进它。首先,音频分类模型分类 35 个不同的标签。我们可以使用一个更小、更轻的二元分类模型,它只预测唤醒词是否被说出。其次,我们提前加载所有模型并让它们在我们的设备上运行。如果我们要省电,我们只会在需要时加载每个模型,并在之后卸载它们。第三,我们的转录函数中缺少语音活动检测模型,它以固定的时间进行转录,在某些情况下时间太长,而在另一些情况下又太短。

通用化到任何事物 🪄

到目前为止,我们已经了解了如何使用语音助手 Marvin 生成语音输出。最后,我们将演示如何将这些语音输出通用化为文本、音频和图像。

我们将使用Transformers Agents来构建我们的助手。Transformers Agents 在 🤗 Transformers 和 Diffusers 库的基础上提供了自然语言 API,它使用 LLM 和精心设计的提示来解释自然语言输入,并使用一组精选工具来提供多模态输出。

我们来实例化一个代理。Transformers Agents 有三种 LLM 可用,其中两种是开源且在 Hugging Face Hub 上免费。第三种是 OpenAI 的模型,需要 OpenAI API 密钥。在此示例中,我们将使用免费的Bigcode Starcoder模型,但您也可以尝试其他可用的 LLM。

from transformers import HfAgent

agent = HfAgent(
    url_endpoint="https://api-inference.huggingface.co/models/bigcode/starcoder"
)

要使用代理,我们只需调用 `agent.run` 并传入我们的文本提示。举例来说,我们将让它生成一张猫的图像 🐈(希望它看起来比这个表情符号好一点)。

agent.run("Generate an image of a cat")
请注意,首次调用此函数将触发模型权重的下载,这可能需要一些时间,具体取决于您的 Hub 下载速度。

就这么简单!代理解释了我们的提示,并在幕后使用稳定扩散来生成图像,而我们无需担心加载模型、编写函数或执行代码。

现在我们可以用 Transformer Agent 替换语音助手中的 LLM 查询函数和文本合成步骤,因为 Agent 将为我们完成这两项步骤。

launch_fn()
transcription = transcribe()
agent.run(transcription)

尝试说出相同的提示“生成一张猫的图片”,看看系统如何响应。如果您向 Agent 提出一个简单的问答查询,Agent 将以文本形式回答。您可以通过要求它返回图片或语音来鼓励它生成多模态输出。例如,您可以要求它:“生成一张猫的图片,添加标题,并朗读标题。”

虽然 Agent 比我们第一个迭代的 Marvin 🤖 助手更灵活,但以这种方式概括语音助手任务可能会导致在标准语音助手查询上的性能下降。为了恢复性能,您可以尝试使用性能更好的 LLM 检查点,例如 OpenAI 的检查点,或者定义一组特定于语音助手任务的自定义工具

< > 在 GitHub 上更新