Moonshine 开源语音识别超越 Whisper
Moonshine STT 模型在准确率和性能上突破 Whisper Large v3,为语音应用开发者提供更优的开源方案。
Moonshine STT 模型在准确率和性能上突破 Whisper Large v3,为语音应用开发者提供更优的开源方案。
面向所有人的语音界面
什么时候应该选择 Moonshine,而不是 Whisper?
使用库 语音转文本 文本转语音 对话式智能体
对话式智能体
Moonshine Voice 是一套开源 AI 工具包,面向构建实时语音智能体和应用程序的开发者。
一切都在设备端运行,因此速度快、隐私性强,而且你不需要账户、信用卡或 API 密钥。
该框架和模型针对实时流式应用进行了优化,会在用户仍在说话时完成大量处理工作,从而提供低延迟响应。
所有语音转文本模型都基于我们的前沿研究,并从零开始训练,因此从高端模型上超越 Whisper Large V3 的准确率,到适用于资源受限部署、体积仅 1MB 的微型模型,我们都能提供。
它可以轻松集成到不同平台中,同一个库能够运行在 Python、iOS、Android、MacOS、Linux、Windows、Raspberry Pi、IoT 设备、微控制器、DSP 和可穿戴设备上。
开箱即用。它的高级 API 为转录、文本转语音、声音克隆、说话人识别(说话人分离)、命令识别和对话式智能体等常见任务提供完整解决方案,因此你可以只使用一个库来构建语音应用。
它支持多种语言:STT 支持英语、西班牙语、普通话、日语、韩语、越南语、乌克兰语和阿拉伯语;TTS 支持英语、西班牙语、阿拉伯语、德语、法语、印地语、意大利语、日语、韩语、荷兰语、葡萄牙语、俄语、土耳其语、乌克兰语、越南语和普通话。
加入我们的 Discord 社区,获取实时支持。
适用于 iOS、Android、macOS、Windows 和 Raspberry Pi 的示例应用已在 GitHub Releases 中以独立压缩包的形式发布(大多采用 {platform}-{Project}.tar.gz 命名,与 examples/ 下的文件夹名称对应;Windows 还为 C++ 示例提供了 moonshine-voice-windows-x86_64.tar.gz)。完整的发布下载列表请参阅“示例”部分。
pip install moonshine-voice
moonshine-voice mic --language en
监听麦克风,并在收到转录更新时将其打印出来。
moonshine-voice dialog
运行一段用于设置 Wi-Fi 的语音对话:它会监听触发短语、提出问题并确认答案。匹配基于语义,因此能够识别自然语言的不同表达方式。要了解更多信息,请查看我们的“入门”Colab notebook 和视频。
moonshine-voice tts --language en_us --text "Hello world"
合成并朗读文本。
下载 github.com/moonshine-ai/moonshine/releases/latest/download/ios-Transcriber.tar.gz,解压后在 Xcode 中打开 Transcriber/Transcriber.xcodeproj 项目。
下载 github.com/moonshine-ai/moonshine/releases/latest/download/android-Transcriber.tar.gz,解压后在 Android Studio 中打开 Transcriber 文件夹。
Moonshine Voice 为 x86_64 和 arm64 Linux 都提供了预构建的共享库。最快的体验方式是使用可移植的 C++ 示例:它会下载库、一个英语语音转文本模型和一段示例录音,然后构建并运行转录程序:
curl -O -L https://github.com/moonshine-ai/moonshine/releases/download/v0.1.0/cpp-examples.tar.gz
tar xzf cpp-examples.tar.gz
cd c++
./download-library.sh
g++ transcriber.cpp -Imoonshine-voice/include -Lmoonshine-voice/lib -lmoonshine -Wl,-rpath,'$ORIGIN/moonshine-voice/lib' -o transcriber
./transcriber
Moonshine Voice 同时支持 Apple Silicon(arm64)和 Intel(x86_64)Mac。
下载 github.com/moonshine-ai/moonshine/releases/latest/download/macos-MicTranscription.tar.gz,解压后在 Xcode 中打开 MicTranscription/MicTranscription.xcodeproj 项目。
下载 github.com/moonshine-ai/moonshine/releases/latest/download/windows-cli-transcriber.tar.gz,解压后在 Visual Studio 中打开 cli-transcriber\cli-transcriber.vcxproj 项目。
这是一个自包含的压缩包,其中包含库和模型,因此按 Ctrl+Shift+B 或 F7 即可构建可执行文件。
你需要接入一个 USB 麦克风才能获得音频输入,不过 Python pip 包已经针对 Pi 进行了优化,因此可以运行:
sudo pip install --break-system-packages moonshine-voice
moonshine-voice mic --language en
我在 YouTube 上录制了一个屏幕演示视频,帮助你快速入门。你还可以下载 github.com/moonshine-ai/moonshine/releases/latest/download/raspberry-pi-my-dalek.tar.gz,体验一些有趣的 Pi 专属示例。如果你不想使用 --break-system-packages,README 中提供了关于使用虚拟环境安装 Python 包的信息。
你可以查看 github.com/moonshine-ai/pi-help-bot,了解一个更高级的示例。
简而言之——当你需要处理实时语音时。
关于这些数据的测量方式,请参阅基准测试。
OpenAI 发布 Whisper 模型家族,是开源语音转文本领域的一次巨大飞跃。它提供了多种模型尺寸,让开发者能够在计算量、存储空间和准确率之间做出权衡,以适配自己的应用。其最大的模型(如 Large v3)还实现了极高的准确率,超过了 Google 或 Apple 等大型科技公司之外此前可获得的任何方案。在 Moonshine,我们很早就开始积极采用 Whisper,至今仍然非常喜欢这些模型,以及围绕它们构建的 FasterWhisper 等优秀框架。
然而,在构建需要实时语音界面的应用时,我们发现自己需要一些 Whisper 无法提供的能力:
Whisper 始终使用 30 秒的输入窗口。在批量处理音频时,这不是什么问题,因为通常可以在文件中向前查看,找到一段约 30 秒的语音交给它处理。语音界面无法预先查看输入流,从而将输入拼成更大的片段,而且一句话很少会超过 5 到 10 秒。这意味着编码器和解码器会把大量计算浪费在处理补零数据上,从而导致更长的结果返回延迟。由于任何界面最重要的要求之一都是响应速度——通常定义为延迟低于 200ms——因此,即使在计算资源充足的平台上,这也会损害用户体验;而在资源更受限的设备上,它甚至会变得不可用。
Whisper 不会缓存任何内容。语音界面的另一个常见要求是,在用户说话时显示反馈,让用户知道应用正在倾听并理解他们。这意味着在一句话说出的过程中,需要随时间推移反复调用语音转文本模型。大部分音频输入其实都是相同的,只有末尾新增了一小段内容。尽管大量输入保持不变,Whisper 每次仍然会从头开始处理,对之前已经处理过的音频重复执行大量工作。与固定输入窗口一样,这种不必要的延迟也会损害用户体验。
Whisper 对很多语言的支持效果不佳。Whisper 的多语言支持是一项令人惊叹的工程成就,它证明了单个模型可以处理多种语言,甚至可以提供翻译能力。这张来自 OpenAI 的图表(原始数据见附录 D-2.4)展示了即便使用最大的 15 亿参数模型,词错误率(WER)仍会如何随语言变化而恶化。
图中列出了 82 种语言,但只有 33 种语言的 WER 低于 20%(这是我们认为可用的水平)。对于边缘设备上常用的 Base 模型尺寸,只有 5 种语言的 WER 低于 20%。韩语和日语等亚洲语言尤其引人注意,因为它们是拥有庞大市场和大量技术创新地区的母语,但 Whisper 的准确率不足以用于大多数应用。通过 OpenAI 云端 API 提供的 Whisper 内部专有版本似乎拥有更高的准确率,但它们并未作为开放模型提供。
边缘平台支持碎片化。Whisper 周围已经形成了一个非常出色的生态系统,你可以使用许多成熟框架来部署模型。不过,这些框架往往主要面向桌面级机器和操作系统。虽然也有一些项目可以用于 iOS、Android 或 Raspberry Pi OS 等边缘平台,但它们通常拥有不同的接口、能力和优化水平。这让需要运行在多种设备上的应用开发变得不必要地困难。
正是这些限制促使我们创建了自己的模型家族,以更好地满足实时语音界面的需求。这花费了我们一些时间,因为与源自 Web 的文本数据量相比,可用开放语音数据集的总体规模非常小。不过,在开展了大量数据收集工作之后,我们终于得以发布第一代 Moonshine 模型。这些模型消除了固定输入窗口的限制,并带来了其他一些架构改进,使其在实时语音应用中的延迟显著低于 Whisper,运行速度通常可以快 5 倍甚至更多。
然而,我们不断遇到一些应用,它们需要在资源更受限的平台上实现更低的延迟。我们还希望提供比初代模型中最高规格、相当于 Base 级别的模型更高的准确率。因此,我们推出了第二代 Moonshine 模型,它们具备以下特性:
灵活的输入窗口。你可以提供任意长度的音频(不过我们建议控制在 30 秒左右以内),模型只会对这段输入执行计算,无需补零。这显著降低了延迟。
流式处理缓存。我们的模型现在支持随时间增量添加音频,并会缓存输入编码以及解码器的部分状态,从而跳过更多计算,大幅降低延迟。
语言专用模型。我们收集了数据,并针对多种语言训练了模型,包括阿拉伯语、日语、韩语、西班牙语、乌克兰语、越南语和中文。正如我们在 Flavors of Moonshine 论文中讨论的那样,我们发现,与用一个模型训练多种语言相比,如果让模型只专注于一种语言,就能在相同模型大小和计算量下获得高得多的准确率。
跨平台库支持。我们自己也在构建应用,因此需要能够将这些模型部署到 Linux、MacOS、Windows、iOS 和 Android,并能通过 Python、Swift、Java 和 C++ 等语言使用。为此,我们设计了一个可移植的 C++ 核心库来处理所有流程,使用 OnnxRuntime 在不同系统上实现良好性能,并为所有需要的高级语言构建了原生接口。这样,开发者只需学习一套 API,就能将其部署到几乎任何希望运行的平台上。
准确率优于 Whisper V3 Large。在 HuggingFace 的 OpenASR 排行榜上,我们最新的英语流式模型 Medium Streaming,取得了比 OpenAI 准确率最高的 Whisper 模型更低的词错误率。Moonshine 的这个版本仅使用 2.5 亿个参数,而 Large v3 使用了 15 亿个参数,因此 Moonshine 更容易部署在边缘设备上。
希望这些内容能让你充分了解 Moonshine 与 Whisper 的对比。如果你使用云端 GPU 批量处理数据,并且吞吐量最为重要,那么 Whisper(或 Nvidia 的 Parakeet 等替代方案)具备批处理之类的优势;但对于实时语音,我们相信 Moonshine 无可匹敌。我们构建了自己刚开始开发语音界面应用时梦寐以求的框架和模型,因此,如果你正在处理实时语音输入,不妨试试 Moonshine。
Moonshine API 旨在处理实时语音采集和转录的各种细节,为应用开发者提供专注于可操作事件的高级 API。下面我会用 Python 说明其工作方式,不过这套 API 在所有受支持的语言中都保持一致。
转录入门 转录事件流
转录事件流
对话式智能体入门 智能体设置
文本转语音入门 语音示例 ZipVoice Kokoro Piper TTS 将字素转换为音素
语音示例 ZipVoice Kokoro Piper TTS
将字素转换为音素
将库添加到你自己的应用中
调试 控制台日志 输入保存 API 调用日志
从源码构建 Cmake 语言绑定 移植
下载模型 语音转文本模型 意图识别模型 文本转语音模型
语音转文本模型
意图识别模型
文本转语音模型
我们的目标是构建一个任何开发者都能轻松上手使用的框架,即使此前完全没有语音技术方面的经验也没问题。我们抽象掉了大量不必要的细节,并提供了简单的接口,让你能够专注于构建应用,这一点也体现在我们的系统架构中。
如果你想获得说话的文本,请创建一个 Transcriber 对象;如果只需要知道用户请求了某项操作,则创建一个 DialogFlow。
添加一个 EventListener,让它在发生重要事件时被调用,例如一个短语结束或某项操作被触发,以便你的应用作出响应。
使用 TextToSpeech 对象实现双向对话。
传统上,为应用或产品添加语音界面,需要集成许多不同的库,以完成采集音频并将其转换为可操作信息所需的全部处理。主要步骤包括麦克风采集、语音活动检测(将连续音频流拆分成语音片段)、语音转文本、说话人识别和意图识别。通常,每个步骤都需要使用不同的框架,这大大增加了集成、优化和维护这些依赖项的复杂度。
Moonshine Voice 将所有这些阶段整合到单个库中,并抽象掉除应用响应用户语音所需关键信息之外的所有内容,无论你是想转录语音,还是触发操作。
大多数开发者应该可以将这个库视为一个黑盒:当有值得关注的事件发生时,它会通知应用。你可以使用我们基于事件的类来实现应用逻辑。当然,该框架完全开源,因此语音专家可以根据需要深入研究其内部实现,但使用它并不要求这样做。
Transcriber 接收音频输入,并将其中的所有语音转换为文本。这是使用 Moonshine 时需要创建的第一个对象,你需要向它提供已下载模型的路径。
MicTranscriber 是基于通用 transcriber 的辅助类,负责利用所在平台的内置支持连接麦克风(例如 Python 中的 sounddevice),然后在采集音频的同时将其送入处理流程。
Stream 是音频输入的处理器。之所以存在 stream,是因为你可能希望同时处理多个音频输入,而一个 transcriber 可以通过多个 stream 支持这些输入,无需复制模型资源。如果只有一个输入,transcriber 类本身提供了与 stream 相同的方法(start/stop/add_audio),你可以直接使用该接口,无须考虑 stream。
TranscriptLine 是一种数据结构,用于保存转录文本中某一行的信息。当有人说话时,库会等待短暂停顿(即书面语言中可能出现标点符号的位置),然后开始新的一行。这些行并不完全等同于句子,因为语音中的停顿并不一定意味着句子已经结束,但这种方式确实能将口述音频拆分为可视作短语的片段。一行中包含该行是否刚刚开始、是否仍在说话、是否已经完成等状态,以及开始时间和持续时长。
Transcript 是一个按时间顺序排列的行列表,保存已经识别出的文本信息,以及采集时间等其他状态。
TranscriptEvent 包含转录文本发生变化时的相关信息。事件包括开始新的一行、更新某一行中的文本,以及某一行完成。事件对象包含它所指向的 transcript line 作为成员,其中保存该行的最新状态。
TranscriptEventListener 是一种协议,允许在发生 transcript event 时调用由应用定义的函数。这是大多数应用与转录结果交互的主要方式。实时语音发生时,应用通常需要在识别出新语音后作出响应或显示结果。通过这种方式,你可以像处理传统用户界面中的事件一样处理这些变化,例如触摸屏手势或点击按钮。
TextToSpeech 对象负责合成音频并播放给用户。
DialogFlow 对象负责管理用户与智能体之间的对话。它同时也是一个 TranscriptEventListener,因此你可以将它附加到 transcriber 上,并让它在有人说出与已注册短语含义相近的内容时调用回调——这是语音命令识别的基础。
每次对话交互都会创建一个 Dialog 对象,让智能体能够与用户进行多步骤讨论。
我们为大多数平台都提供了示例,因此第一步,我建议先查看针对目标系统提供的内容。
接下来,你需要将这个库添加到项目中。我们的目标是通过各个平台的原生包管理器,为所有主流平台提供预构建二进制文件。在 Python 上,这意味着使用 pip install;在 Android 上,它是一个 Maven 包;而对于 MacOS 和 iOS,我们通过 SPM 提供 Swift 包。
转录器需要访问你所使用模型的文件,因此下载完成后,你需要将它们放在应用程序能够找到的位置,并记下该路径。如果你打算分发应用程序,这通常意味着要在 IDE 中将它们添加为资源;如果只是进行实验,也可以使用硬编码路径。下载脚本执行完成后,会显示模型在磁盘上的位置及其架构类型。
现在可以尝试创建一个转录器。以下是 Python 中的写法:
transcriber = Transcriber(model_path=model_path, model_arch=model_arch)
如果找不到模型,或者发生任何其他错误,这段代码将抛出异常,并提供有关问题的信息。你还可以在控制台中查看核心库输出的日志,这些日志会被打印到 stderr 或系统中的等效位置。
接下来,我们将创建一个监听器,其中包含你希望在转录文本更新时触发的应用逻辑,然后将其附加到转录器:
class TestListener(TranscriptEventListener):
def on_line_started(self, event):
print(f"Line started: {event.line.text}")
def on_line_text_changed(self, event):
print(f"Line text changed: {event.line.text}")
def on_line_completed(self, event):
print(f"Line completed: {event.line.text}")
transcriber.add_listener(listener)
转录器需要一些音频数据才能工作。如果你想通过麦克风进行尝试,可以修改创建转录器的代码,改用 MicTranscriber;但如果你想先使用 .wav 文件进行测试,可以按以下方式输入音频:
audio_data, sample_rate = load_wav_file(wav_path)
transcriber.start()
# Loop through the audio data in chunks to simulate live streaming
# from a microphone or other source.
chunk_duration = 0.1
chunk_size = int(chunk_duration * sample_rate)
for i in range(0, len(audio_data), chunk_size):
chunk = audio_data[i: i + chunk_size]
transcriber.add_audio(chunk, sample_rate)
transcriber.stop()
这里需要注意的要点包括:
我们使用 Moonshine 库提供的便捷函数 load_wav_file(),从 wav 文件创建一个单声道音频数据数组。
我们启动转录器,以激活其处理代码。
循环以数据块的形式添加音频。这些数据块可以具有任意长度和任意采样率,库会负责处理所有相关的繁杂工作。
随着音频不断加入,你添加的事件监听器将被调用,并提供有关最新语音的信息。
在实际应用中,你会从接收音频源数据的音频处理程序中调用 add_audio()。由于该库可以处理任意时长和采样率,因此只需确保音频为单声道,除此之外直接按原样输入即可。
默认情况下,转录器每收到 500ms 的输入就会分析一次语音。你可以通过转录器构造函数的 update_interval 参数更改这一间隔。对于流式模型,大部分工作会在添加音频时完成,并且在短语结束时自动执行,因此更改此参数通常不会对工作负载或延迟产生特别大的影响。
关键在于,你通常不需要关心转录文本的数据结构本身,事件系统会在重要情况发生时通知你。如果确实需要检查状态,可以调用 update_transcription() 手动触发转录更新。该方法会返回一个转录对象,其中包含当前会话的全部信息。
通过对转录器(或流)调用 start() 和 stop(),我们可以开始和结束一个会话。每个会话都关联一个转录文档,并且每次调用 start() 时都会重新创建,因此在此之前,你应该复制转录对象中所有需要保留的数据。
对于已经拥有一组历史数据、只想对其进行分析的情况,例如文件或录音,转录器类还提供了一个更简单的 transcribe_without_streaming() 方法。
我们还提供了基础 Transcriber 类的一个专用实现,名为 MicTranscriber。它的具体实现取决于语言和平台,但应该提供一个自动连接到系统主麦克风的转录器。由于它支持与基础类相同的所有监听器回调,因此可以非常方便地开始转录来自这一常用音频源的语音。
库与你的应用程序之间的主要通信渠道是事件,这些事件会传递给你注册的所有监听器函数。主要有五种事件类型:
LineStarted。当检测到一个新语音片段的开头时,会将此事件发送给监听器。它可能包含文本,也可能不包含;但由于它会在一次话语刚开始时分发,因此其中的文本很可能会随时间发生变化。
LineUpdated。每当某一行的任何信息发生变化时调用,包括持续时间、音频数据和文本。
LineTextChanged。仅当与某一行关联的文本更新时调用。它是 LineUpdated 的一个子集,主要满足这样一种常见需求:尽可能频繁地刷新向用户显示的文本,从而保持交互体验。
LineSpeakersChanged。仅在启用了可选的 identify_speakers 选项时触发。当附加到某一行的说话人区间发生变化时调用。与其他行事件不同,它可能会针对已经完成的行触发,因为随着更多音频到达,说话人分离算法会持续优化其说话人分配结果。
LineCompleted。当检测到有人暂停说话并结束当前片段时发送。此时,行数据结构中的文本和持续时间均为最终值。
我们对这些事件提供以下保证:
对于任何片段,LineStarted 始终只会调用一次。
对于任何片段,LineCompleted 始终会在 LineStarted 之后调用,并且只调用一次。
对于某个片段,LineUpdated 和 LineTextChanged 只会在 LineStarted 事件之后、LineCompleted 事件之前调用。
这些更新事件不保证一定会被调用(实际使用中,可以通过将 update_interval 设置为一个非常大的值来禁用它们)。
对于任何给定的流,同一时间只会有一行处于活动状态。
调用 LineCompleted 后,库将不再修改该行的文本、时间信息或音频数据。唯一的例外是该行的说话人区间:启用 identify_speakers 后,近期音频的说话人区间可能会被修订(通过 LineSpeakersChanged 发出信号),因为 diar