创建语音助手
在本节中,我们将把已经亲身体验过的三个模型整合在一起,构建一个名为 Marvin 🤖 的端到端语音助手。与亚马逊的 Alexa 或苹果的 Siri 一样,Marvin 是一款虚拟语音助手,它会响应特定的“唤醒词”,然后监听口语查询,最后以口语回答进行响应。
我们可以将语音助手管道分解为四个阶段,每个阶段都需要一个独立的模型
1. 唤醒词检测
语音助手会不断监听从设备麦克风传入的音频输入,但它们只会在说出特定的“唤醒词”或“触发词”时启动操作。
唤醒词检测任务由一个小型设备上音频分类模型处理,该模型比语音识别模型小得多,轻得多,通常只有几百万个参数,而语音识别模型则有数亿个参数。因此,它可以在您的设备上持续运行,而不会耗尽电池。只有在检测到唤醒词时才会启动更大的语音识别模型,然后它会再次关闭。
2. 语音转录
管道中的下一个阶段是将口语查询转录为文本。实际上,由于音频文件体积庞大,将音频文件从本地设备传输到云的速度很慢,因此在设备上直接使用自动语音识别 (ASR) 模型进行转录比使用云中的模型更有效。设备上的模型可能比云中托管的模型更小,因此精度较低,但更快的推理速度使其变得有意义,因为我们可以以接近实时的速度运行语音识别,在我们说话时转录我们的口语音频片段。
我们现在已经非常熟悉语音识别流程,所以这应该很简单!
3. 语言模型查询
现在我们知道了用户问了什么,我们需要生成一个响应!最适合此任务的模型是 *大型语言模型 (LLM)*,因为它们能够有效地理解文本查询的语义并生成合适的响应。
由于我们的文本查询很小(只有几个文本标记),而语言模型很大(数十亿个参数),因此运行 LLM 推理最有效的方式是将文本查询从我们的设备发送到云中运行的 LLM,生成文本响应,然后将响应返回到设备。
4. 合成语音
最后,我们将使用文本到语音 (TTS) 模型将文本响应合成为口语。这在设备上完成,但您也可以在云中运行 TTS 模型,生成音频输出并将其传输回设备。
同样,我们已经做过很多次了,所以这个过程会非常熟悉!
唤醒词检测
语音助手管道中的第一步是检测是否说出了唤醒词,我们需要为自己找到一个合适的预训练模型来完成此任务!您会记得关于 用于音频分类的预训练模型 的部分,语音命令 是一个语音词数据集,旨在评估音频分类模型对 15 个以上简单命令词(如 "up"
、"down"
、"yes"
和 "no"
)以及 "silence"
标签进行分类,以分类无语音。花点时间在 Hub 上的数据集查看器中收听样本,并重新熟悉语音命令数据集:数据集查看器.
我们可以使用在 Speech Commands 数据集上预训练的音频分类模型,并从中选择一个简单的命令词作为唤醒词。在 15 个以上的可能的命令词中,如果模型预测我们选择的唤醒词的概率最高,我们就可以相当确定唤醒词已经被说出来了。
让我们前往 Hugging Face Hub 并点击“Models”标签:https://huggingface.co/models
这将显示 Hugging Face Hub 上的所有模型,按照过去 30 天的下载量排序。
你会注意到在左侧,我们有一系列标签供我们选择,用来根据任务、库、数据集等过滤模型。向下滚动并从音频任务列表中选择任务“音频分类”。
现在,我们看到了 Hub 上 500 多个音频分类模型的子集。为了进一步细化这个选择,我们可以根据数据集过滤模型。点击“Datasets”标签,并在搜索框中输入“speech_commands”。当你开始输入时,你会看到搜索标签下方出现 speech_commands
的选择。你可以点击这个按钮,将所有音频分类模型过滤为在 Speech Commands 数据集上微调的模型。
太棒了!我们看到,对于这个特定数据集和任务,我们有六个预训练模型可以使用(尽管如果你在稍后的日期阅读,可能会有新的模型添加)。你会认出第一个模型,它是我们曾在第 4 单元示例中使用的 音频频谱图转换器检查点。我们将在我们的唤醒词检测任务中再次使用此检查点。
让我们使用 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”,或者苹果的 Siri 使用“Hey 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'}
太棒了!正如预期的那样,模型在最初的几秒钟内生成了垃圾预测。没有语音输入,因此模型做出的预测接近随机,但概率非常低。一旦我们说出唤醒词,模型就会预测 "marvin"
的概率接近 1,并终止循环,表明唤醒词已被检测到,ASR 系统应该被激活!
语音转录
我们再次使用 Whisper 模型作为我们的语音转录系统。具体来说,我们将加载 Whisper Base 英文 检查点,因为它足够小,可以在合理的转录准确率下提供良好的推理速度。我们将使用一个技巧来实现近乎实时的转录,方法是巧妙地将我们的音频输入转发给模型。与之前一样,请随意使用 Hub 上的任何语音识别检查点,包括 Wav2Vec2、MMS ASR 或其他 Whisper 检查点。
transcriber = pipeline(
"automatic-speech-recognition", model="openai/whisper-base.en", device=device
)
"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,并使用 Inference API 来轻松查询模型。
首先,让我们前往 Hugging Face Hub。为了找到我们的 LLM,我们将使用 🤗 Open LLM 排行榜,这是一个根据四个生成任务的性能对 LLM 模型进行排名的空间。我们将通过“instruct”进行搜索,以过滤掉经过指令微调的模型,因为这些模型应该更适合我们的查询任务。
我们将使用 tiiuae/falcon-7b-instruct 检查点,该检查点由 TII 提供,它是一个 70 亿参数的仅解码器 LM,在混合聊天和指令数据集上进行了微调。你可以在 Hugging Face Hub 上使用任何启用了“托管推理 API”的 LLM,只需注意模型卡片右侧的小部件即可。
Inference API 允许我们从本地机器向托管在 Hub 上的 LLM 发送 HTTP 请求,并将响应作为 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'
你会注意到使用 Inference 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 解释自然语言输入,并使用一组精心挑选的工具提供多模态输出。
让我们继续实例化一个代理。有三个可用的 LLM 用于 Transformers Agents,其中两个是开源的,可以在 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")
就这么简单!代理解释了我们的提示,并在幕后使用了Stable Diffusion 生成图像,而我们无需担心加载模型、编写函数或执行代码。
现在,我们可以用我们的 Transformers Agent 替换语音助手中的 LLM 查询函数和文本合成步骤,因为代理将为我们处理这两个步骤。
launch_fn() transcription = transcribe() agent.run(transcription)
试着说出相同的提示“生成一张猫的图像”,看看系统如何运行。如果你问代理一个简单的问答查询,代理将用文本答案回复。你可以通过要求它返回图像或语音来鼓励它生成多模态输出。例如,你可以要求它:“生成一张猫的图像,为它加上标题,并说出标题”。
虽然代理比我们之前的 Marvin 🤖 助手更灵活,但以这种方式泛化语音助手任务可能会导致标准语音助手查询的性能下降。为了恢复性能,你可以尝试使用性能更高的 LLM 检查点,例如来自 OpenAI 的检查点,或者定义一组自定义工具,这些工具专门针对语音助手任务。