ウェイクワードエンジンの導入
openwakeWordと EffecientWord-Net について調べる
1. OpenWakeWord
開発元: Picovoice(またはオープンソースコミュニティ)
特徴
* オープンソースで利用可能
* Pythonで簡単に組み込める
* 自作ウェイクワードの訓練が比較的簡単(マイク音声から自作ワードを学習可能)
* 低スペックなデバイス(例:Raspberry Pi)でも動作可能
* 英語中心だが、日本語でも利用できるようカスタムワードを収録可
メリット
* 手軽に組み込みやすい
* 小型音声アシスタントやプロトタイプ向けに最適
* 小型モデルなのでリアルタイム処理に強い
利用例
* 「ねえラマ!」など任意のフレーズで起動する家庭用AIスピーカー
* 子供の声でも誤検知しにくいように調整可能
2. EfficientWord-Net
開発元: ByteDance Research(論文あり)
特徴
* 学習済みモデルを使用した高精度ウェイクワード検出モデル
* EfficientNetアーキテクチャベースで構築されており、精度と軽量性を両立
* 公開されている論文ベースでは、LibriSpeechやGoogle Speech Commandsなどのベンチマークで優秀な精度を記録
メリット
* 音声ノイズ環境でも精度が高い
* 高速かつ高精度な推論が可能
* 多数のウェイクワードを同時に検出するシナリオに強い
注意点
* 実装がやや高度で、デバイスへの組み込みにはある程度の知識が必要
* ソースコードが論文公開のみで、実装例が少ないこともある
| 比較項目 | OpenWakeWord | EfficientWord-Net |
| ——— | ——————- | ——————– |
| オープンソース | はい | 一部のみ(論文あり) |
| カスタムワード対応 | あり(簡易に追加可能) | 理論上可能だが要学習 |
| リアルタイム性 | 高い(軽量) | 高いがややGPU依存 |
| 対応言語 | 任意(自作可能、日本語対応も工夫で可) | 主に英語想定 |
| 実装のしやすさ | 非常に簡単(Pythonで完結) | やや難(自前でモデル導入や音響処理必要) |
| 小型デバイス対応 | Raspberry Pi等でOK | 要スペック(モバイルでも可能) |
EfficientWord-Netはリアルタイム性
高いがややGPU依存だと
OpenWakeWordの方が今後ラズパイとかで動く方が良いかも
OpenWakeWordは自分の声でトリガーワードを録音し、モデルを訓練して追加可能
なお自分の声でなくても voicevox などで生成した音声でもいけるらしい
ただし要求するマシンスペックがメモリ8GBのため
Google Colab を使うことにする
ウェイクワード検出ライブラリ openWakeWord を使ってみた
での動作環境は ubuntu
オープンソースのウェイクワード検出ライブラリ「openWakeword」を試す
だと
Apple Silicon
だと問題があるらしい
自力でビルドすればいけるらしいが
GPTだと
Zennのkun432氏による記事「オープンソースのウェイクワード検出ライブラリ『openWakeword』を試す」で報告されていた問題は、現在では解決されています。
当初、Apple Silicon(M1/M2)搭載のMacでOpenWakeWordを使用する際、TensorFlow Liteランタイム(tflite-runtime)が提供されておらず、エラーが発生していました。
しかし、OpenWakeWordはONNX形式のモデルもサポートしており、onnxruntimeを使用することで、Apple Silicon環境でも問題なく動作します。
ってあるけど本当かは試さないと不明
pip install openwakeword onnxruntime sounddevice
でインストール
M1環境では tflite-runtime は使わず、onnxruntime を使うらしい
次に起動テスト
touch test_openwakeword.py
内容を
import sounddevice as sd
from openwakeword.model import Model
# ONNXモードを明示
oww_model = Model(
wakeword_models=["hey_jarvis"],
inference_framework="onnx"
)
sample_rate = 16000
duration = 0.5 # 秒
print("ウェイクワード検出開始(Ctrl+Cで終了)")
try:
while True:
audio = sd.rec(int(sample_rate * duration), samplerate=sample_rate, channels=1, dtype='float32')
sd.wait()
prediction = oww_model.predict(audio.flatten())
score = prediction.get("hey_jarvis", 0)
if score > 0.5:
print(f"検出!スコア: {score:.2f}")
except KeyboardInterrupt:
print("終了しました")
これを実行すると
Traceback (most recent call last):
File "/Users/snowpool/aw10s/gemma/test_openwakeword.py", line 5, in <module>
oww_model = Model(
^^^^^^
File "/Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages/openwakeword/utils.py", line 686, in wrapped
return func(*args, **new_kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages/openwakeword/model.py", line 153, in __init__
self.models[mdl_name] = ort.InferenceSession(mdl_path, sess_options=sessionOptions,
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages/onnxruntime/capi/onnxruntime_inference_collection.py", line 469, in __init__
self._create_inference_session(providers, provider_options, disabled_optimizers)
File "/Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages/onnxruntime/capi/onnxruntime_inference_collection.py", line 530, in _create_inference_session
sess = C.InferenceSession(session_options, self._model_path, True, self._read_config_from_model)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
onnxruntime.capi.onnxruntime_pybind11_state.NoSuchFile: [ONNXRuntimeError] : 3 : NO_SUCHFILE : Load model from /Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages/openwakeword/resources/models/hey_jarvis_v0.1.onnx failed:Load model /Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages/openwakeword/resources/models/hey_jarvis_v0.1.onnx failed. File doesn't exist
となる
OpenWakeWord が使用しようとしているモデルファイル:
/Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages/openwakeword/resources/models/hey_jarvis_v0.1.onnx
が存在せず、onnx モデルの自動ダウンロードに失敗していることが原因
https://zenn.dev/kun432/scraps/1a987de4943c65
にも書いてあるけど
どうやらモデルファイルをダウンロードする必要がある
GPTでモデルダウンロードURLを表示されるが
存在しないURLだったので解決策を調べる
https://zenn.dev/kun432/scraps/1a987de4943c65
を参考に
touch download.py
内容は
import openwakeword openwakeword.utils.download_models()
で
実行すると
python download.py embedding_model.tflite: 100%|█████████████████| 1.33M/1.33M [00:00<00:00, 9.92MiB/s] embedding_model.onnx: 100%|███████████████████| 1.33M/1.33M [00:00<00:00, 7.13MiB/s] melspectrogram.tflite: 100%|██████████████████| 1.09M/1.09M [00:00<00:00, 6.30MiB/s] melspectrogram.onnx: 100%|████████████████████| 1.09M/1.09M [00:00<00:00, 6.54MiB/s] silero_vad.onnx: 100%|████████████████████████| 1.81M/1.81M [00:00<00:00, 7.82MiB/s] alexa_v0.1.tflite: 100%|████████████████████████| 855k/855k [00:00<00:00, 5.67MiB/s] alexa_v0.1.onnx: 100%|██████████████████████████| 854k/854k [00:00<00:00, 5.36MiB/s] hey_mycroft_v0.1.tflite: 100%|██████████████████| 860k/860k [00:00<00:00, 6.84MiB/s] hey_mycroft_v0.1.onnx: 100%|████████████████████| 858k/858k [00:00<00:00, 6.52MiB/s] hey_jarvis_v0.1.tflite: 100%|█████████████████| 1.28M/1.28M [00:00<00:00, 6.96MiB/s] hey_jarvis_v0.1.onnx: 100%|███████████████████| 1.27M/1.27M [00:00<00:00, 6.26MiB/s] hey_rhasspy_v0.1.tflite: 100%|██████████████████| 416k/416k [00:00<00:00, 4.76MiB/s] hey_rhasspy_v0.1.onnx: 100%|████████████████████| 204k/204k [00:00<00:00, 3.06MiB/s] timer_v0.1.tflite: 100%|██████████████████████| 1.74M/1.74M [00:00<00:00, 7.98MiB/s] timer_v0.1.onnx: 100%|████████████████████████| 1.74M/1.74M [00:00<00:00, 8.96MiB/s] weather_v0.1.tflite: 100%|████████████████████| 1.15M/1.15M [00:00<00:00, 6.99MiB/s] weather_v0.1.onnx: 100%|██████████████████████| 1.15M/1.15M [00:00<00:00, 6.44MiB/s]
となってダウンロードされる
再度
python test_openwakeword.py ウェイクワード検出開始(Ctrl+Cで終了) ^C終了しました
と起動はするけど
ジャービス
ヘイ ジャービス
でも反応がない
https://zenn.dev/kun432/scraps/1a987de4943c65
のコードを参考に実験する
import pyaudio
import numpy as np
from openwakeword.model import Model
import sys
FORMAT = pyaudio.paInt16
CHANNELS = 1
RATE = 16000
CHUNK = 1024
audio = pyaudio.PyAudio()
mic_stream = audio.open(format=FORMAT, channels=CHANNELS, rate=RATE, input=True, frames_per_buffer=CHUNK)
model_name = "alexa_v0.1.onnx"
model = Model(
wakeword_models=[model_name],
inference_framework="onnx"
)
print("Listening for wakeword \"Alexa\"...")
print()
prev_detect=False
while True:
audio = np.frombuffer(mic_stream.read(CHUNK), dtype=np.int16)
prediction = model.predict(audio)
scores = model.prediction_buffer[model_name]
curr_score = format(scores[-1], '.20f')
detect = True if float(curr_score) > 0.5 else False
if detect:
if detect != prev_detect:
print(f"Detected!({curr_score[:5]})")
prev_detect=True
else:
prev_detect=False
を
Alexaから変更する
これは家の中にあるアレクサと被るため
hey jarvisで動作確認する
とりあえず GPTでコード解説
マイク設定と音声入力の初期化
import pyaudio import numpy as np from openwakeword.model import Model import sys FORMAT = pyaudio.paInt16 # 16bit PCM CHANNELS = 1 # モノラル RATE = 16000 # サンプリング周波数 16kHz CHUNK = 1024 # 一度に読み取るサンプル数 audio = pyaudio.PyAudio() mic_stream = audio.open(format=FORMAT, channels=CHANNELS, rate=RATE, input=True, frames_per_buffer=CHUNK)
これで
マイクから音声をリアルタイムで取得するためのストリームを開始
次にOpenWakeWord モデルの初期化 model_name = "alexa_v0.1.onnx" model = Model( wakeword_models=[model_name], inference_framework="onnx" )
今回は”alexa_v0.1.onnx” モデルをONNXランタイムで使用
macOS (M1など) では onnx が推奨
そして
メインループで音声解析
print("Listening for wakeword \"Alexa\"...")
while True:
audio = np.frombuffer(mic_stream.read(CHUNK), dtype=np.int16)
この部分で
マイクから CHUNK = 1024 サンプル(= 約64ms)分を読み込み
int16 に変換してNumPy配列にします(OpenWakeWordの入力形式)
そしてウェイクワードの検出
prediction = model.predict(audio)
現在のフレームに対してウェイクワードスコアを計算
scores = model.prediction_buffer[model_name]
curr_score = format(scores[-1], '.20f')
detect = True if float(curr_score) > 0.5 else False
過去のスコアをバッファから取得し、最新のスコア([-1])を確認
0.5 を超えていたら検出と判定
一度だけ表示する仕組みとして
if detect:
if detect != prev_detect:
print(f"Detected!({curr_score[:5]})")
prev_detect=True
else:
prev_detect=False
前回検出と今回の結果が異なるときだけ表示することで、連続検出を防ぐ
例:「アレクサ」と言う → スコアが上がって「Detected!」が一度表示
これを元に音声モデルを変更
import pyaudio
import numpy as np
from openwakeword.model import Model
import sys
FORMAT = pyaudio.paInt16
CHANNELS = 1
RATE = 16000
CHUNK = 1024
audio = pyaudio.PyAudio()
mic_stream = audio.open(format=FORMAT, channels=CHANNELS, rate=RATE, input=True, frames_per_buffer=CHUNK)
model_name = "hey_jarvis_v0.1.onnx"
model = Model(
wakeword_models=[model_name],
inference_framework="onnx"
)
print("Listening for wakeword \"hey_jarvis\"...")
print()
prev_detect=False
while True:
audio = np.frombuffer(mic_stream.read(CHUNK), dtype=np.int16)
prediction = model.predict(audio)
scores = model.prediction_buffer[model_name]
curr_score = format(scores[-1], '.20f')
detect = True if float(curr_score) > 0.5 else False
if detect:
if detect != prev_detect:
print(f"Detected!({curr_score[:5]})")
prev_detect=True
else:
prev_detect=False
実行したら
python test_openwakeword.py
Traceback (most recent call last):
File "/Users/snowpool/aw10s/gemma/test_openwakeword.py", line 1, in <module>
import pyaudio
ModuleNotFoundError: No module named 'pyaudio'
となったので
pip install pyaudio Collecting pyaudio Downloading PyAudio-0.2.14.tar.gz (47 kB) Installing build dependencies ... done Getting requirements to build wheel ... done Preparing metadata (pyproject.toml) ... done Building wheels for collected packages: pyaudio Building wheel for pyaudio (pyproject.toml) ... done Created wheel for pyaudio: filename=pyaudio-0.2.14-cp311-cp311-macosx_15_0_arm64.whl size=25773 sha256=85950d898df05ebcce820f2f6d30b030db44951656c7decef10274215dca529b Stored in directory: /Users/snowpool/Library/Caches/pip/wheels/80/b1/c1/67e4ef443de2665d86031d4760508094eab5de37d5d64d9c27 Successfully built pyaudio Installing collected packages: pyaudio Successfully installed pyaudio-0.2.14 [notice] A new release of pip is available: 24.3.1 -> 25.1.1 [notice] To update, run: pip install --upgrade pip
で結局反応しないのでアレクサで試したら動いた…
単純に私の発音が悪いらしい
GPTによれば発音がダメとのこと
なお最初にGPTで提案されたコードは動作しないので
今回のコードを使うことにする