Home Assistant Voice PE に日本語ウェイクワードを自作して載せるまで


Home Assistant Voice PE(以下 Voice PE)は、ESPHome ベースでローカル音声アシスタントを動かせる便利なデバイスです。ただし、公式で配布されている wake word モデルは英語中心で、日本語の wake word は自分で用意する必要があります。

この記事では、microWakeWord を使って日本語ウェイクワード「ねえ、マイク」を学習し、ESPHome 経由で Voice PE に組み込むまでの流れをまとめます。

本記事は「とにかく動かしてみる」より、同じ手順を再現したい人向けの内容です。学習にはそれなりの時間とディスク容量を使います。

できたもの

  • 日本語 wake word モデル(量子化済み TFLite): 約62KB
  • パラメータ数: 約26,000
  • マニフェスト: nee_maiku.json
  • Voice PE 向けファームウェアへのビルド・OTA書き込みまで成功

自分の環境では、テストセット上で次のような結果でした。

Cutoff 誤拒否率 誤検知/時間
0.92 6.2% 0.56
0.86 6.2% 0.75
0.74 5.3% 1.5

最終的な probability_cutoff は 0.85 にしています。ただし、合成音声1話者+自分の声33件という小規模なデータなので、実運用での精度は実機で調整が必要です。

全体像

Piperで日本語音声を生成
  ↓
Augmentation(背景音・RIR・ノイズ)
  ↓
スペクトログラム特徴量を生成
  ↓
microWakeWordで学習
  ↓
量子化 → TFLite + JSONマニフェスト
  ↓
ESPHomeでVoice PEに組み込み
  ↓
OTA書き込み → 実機で調整

前提・環境

  • Linux(Ubuntu系)
  • Python 3.10.14
  • CPUのみ(GPUなし)
  • Home Assistant + ESPHome Device Builder アドオン
  • Voice PE のファームウェア: 26.9.0
  • ESPHome: 2026.9.0 以上

Python 3.10 を使うのは、TensorFlow まわりの互換性を考慮してです。

1. 作業ディレクトリとPython環境

mkdir -p ~/mww-ja-work
cd ~/mww-ja-work

~/.pyenv/versions/3.10.14/bin/python3.10 -m venv venv
source venv/bin/activate

pip install -U pip wheel setuptools

microWakeWord 本体をインストールします。

pip install -e /path/to/micro-wake-word

CPU版の PyTorch を先に入れておくと、CUDA版の巨大な依存を避けられます。

pip install torch --index-url https://download.pytorch.org/whl/cpu

Piper と日本語音声合成用の依存を入れます。

pip install piper-tts onnxruntime pyopenjtalk

piper-sample-generator は pip 版だと piper_train が同梱されず失敗することがあるため、GitHub から clone します。

git clone https://github.com/rhasspy/piper-sample-generator.git

2. 日本語Piper音声モデルを用意

Hugging Face から日本語の Piper 音声モデルをダウンロードします。

mkdir -p voices
cd voices

wget -O ja_JP-hi_fi_captain-medium.onnx \
  'https://huggingface.co/rhasspy/piper-voices/resolve/main/ja/ja_JP/hi_fi_captain/medium/ja_JP-hi_fi_captain-medium.onnx?download=true'

wget -O ja_JP-hi_fi_captain-medium.onnx.json \
  'https://huggingface.co/rhasspy/piper-voices/resolve/main/ja/ja_JP/hi_fi_captain/medium/ja_JP-hi_fi_captain-medium.onnx.json?download=true'

日本語モデルが1話者しかないため、話速・ノイズ・長さを変えてバリエーションを増やします。

3. wake wordサンプルを生成

「ねえ、マイク」を2000件生成します。

cd ~/mww-ja-work/piper-sample-generator

PYTHONPATH=. ../venv/bin/python -m piper_sample_generator "ねえ、マイク" \
  --model ../voices/ja_JP-hi_fi_captain-medium.onnx \
  --max-samples 2000 \
  --length-scales 0.8 0.9 1.0 1.1 1.2 \
  --noise-scales 0.5 0.667 0.8 \
  --noise-scale-ws 0.6 0.8 1.0 \
  --output-dir ../generated_samples

生成後、generated_samples/ に WAV が並びます。

4. 背景音・負例を用意

Augmentation 用に RIR と背景音、学習用に負例データセットを取得します。

  • MIT environmental impulse responses
  • FMA(音楽)
  • dinner_party
  • dinner_party_eval
  • no_speech
  • speech

負例は Hugging Face の microWakeWord 用データセットを使います。

cd negative_datasets
link_root="https://huggingface.co/datasets/kahrendt/microwakeword/resolve/main/"

for fname in dinner_party.zip dinner_party_eval.zip no_speech.zip speech.zip; do
  wget -O "$fname" "${link_root}${fname}"
  unzip -q -o "$fname" -d .
done

AudioSet は配布形式が変わっていて取得に失敗したため、今回はスキップしました。FMA と負例セットで代替しています。

5. Augmentationとスペクトログラム生成

Clips → Augmentation → SpectrogramGeneration の順で特徴量を作ります。

from microwakeword.audio.augmentation import Augmentation
from microwakeword.audio.clips import Clips
from microwakeword.audio.spectrograms import SpectrogramGeneration

clips = Clips(
    input_directory='generated_samples',
    file_pattern='*.wav',
    max_clip_duration_s=None,
    remove_silence=False,
    random_split_seed=10,
    split_count=0.1,
)

augmenter = Augmentation(
    augmentation_duration_s=3.2,
    augmentation_probabilities={
        "SevenBandParametricEQ": 0.1,
        "TanhDistortion": 0.1,
        "PitchShift": 0.1,
        "BandStopFilter": 0.1,
        "AddColorNoise": 0.1,
        "AddBackgroundNoise": 0.75,
        "Gain": 1.0,
        "RIR": 0.5,
    },
    impulse_paths=['work/mit_rirs'],
    background_paths=['work/fma_16k'],
    background_min_snr_db=-5,
    background_max_snr_db=10,
    min_jitter_s=0.195,
    max_jitter_s=0.205,
)

spectrograms = SpectrogramGeneration(
    clips=clips,
    augmenter=augmenter,
    slide_frames=10,
    step_ms=10,
)

training / validation / testing の3分割で RaggedMmap に保存します。学習用は repetition 2、validation は 1、testing は 1 にしています。

6. 学習

training_parameters.yaml を作成し、10,000ステップ学習します。

主な設定は以下です。

window_step_ms: 10
clip_duration_ms: 1500
training_steps:
  - 10000
batch_size: 128
learning_rates:
  - 0.001
positive_class_weight:
  - 1
negative_class_weight:
  - 20
maximization_metric: average_viable_recall
target_minimization: 0.9

学習コマンドは以下です。

cd ~/mww-ja-work
nohup ../venv/bin/python -m microwakeword.model_train_eval \
  --training_config='training_parameters.yaml' \
  --train 1 \
  --restore_checkpoint 1 \
  --test_tf_nonstreaming 0 \
  --test_tflite_nonstreaming 0 \
  --test_tflite_nonstreaming_quantized 0 \
  --test_tflite_streaming 0 \
  --test_tflite_streaming_quantized 1 \
  --use_weights "best_weights" \
  mixednet \
  --pointwise_filters "64,64,64,64" \
  --repeat_in_block "1, 1, 1, 1" \
  --mixconv_kernel_sizes '[5], [7,11], [9,15], [23]' \
  --residual_connection "0,0,0,0" \
  --first_conv_filters 32 \
  --first_conv_kernel_size 5 \
  --stride 3 > train.log 2>&1 &

12コアCPUで約32分で完了しました。

7. 自分の声でファインチューニング

合成音声だけだと実際の声に弱いので、自分の声を追加します。

「ねえ、マイク」を33回録音し、16kHzモノラルWAVに変換して my_voice_samples/ に置きます。

clips = Clips(
    input_directory='my_voice_samples',
    file_pattern='*.wav',
    max_clip_duration_s=None,
    remove_silence=True,
    random_split_seed=10,
    split_count=0.15,
)

自分の声は件数が少ないので、repetition を大きくして水増しします。

  • training: 40
  • validation: 10
  • testing: 5

ファインチューニング設定では、自分の声の sampling_weight を 3.0 にし、学習率を 0.0002 に下げます。

training_steps:
  - 12000
learning_rates:
  - 0.0002

既存チェックポイントから2,000ステップ追加学習します。

nohup ../venv/bin/python -m microwakeword.model_train_eval \
  --training_config='training_parameters_finetune.yaml' \
  --train 1 \
  --restore_checkpoint 1 \
  --test_tf_nonstreaming 0 \
  --test_tflite_nonstreaming 0 \
  --test_tflite_nonstreaming_quantized 0 \
  --test_tflite_streaming 0 \
  --test_tflite_streaming_quantized 1 \
  --use_weights "best_weights" \
  mixednet \
  --pointwise_filters "64,64,64,64" \
  --repeat_in_block "1, 1, 1, 1" \
  --mixconv_kernel_sizes '[5], [7,11], [9,15], [23]' \
  --residual_connection "0,0,0,0" \
  --first_conv_filters 32 \
  --first_conv_kernel_size 5 \
  --stride 3 > finetune.log 2>&1 &

8. ESPHome用のモデルとマニフェストを作る

量子化済みの TFLite を取り出します。

cp trained_models/wakeword_finetuned/tflite_stream_state_internal_quant/stream_state_internal_quant.tflite \
   final_model/nee_maiku.tflite

マニフェスト nee_maiku.json を作ります。

{
  "type": "micro",
  "wake_word": "ねえ、マイク",
  "author": "custom",
  "model": "nee_maiku.tflite",
  "trained_languages": ["ja"],
  "version": 2,
  "micro": {
    "probability_cutoff": 0.85,
    "feature_step_size": 10,
    "sliding_window_size": 5,
    "tensor_arena_size": 30000,
    "minimum_esphome_version": "2024.7.0"
  }
}

9. Voice PEへインストール

モデル2ファイルを Home Assistant の /config/esphome/models/ に置きます。

/config/esphome/models/nee_maiku.tflite
/config/esphome/models/nee_maiku.json

ESPHome の YAML に micro_wake_word を追記します。既存の packages はそのまま使います。

substitutions:
  name: home-assistant-voice-xxxxxx
  friendly_name: Home Assistant Voice

packages:
  Nabu Casa.Home Assistant Voice PE: "github://esphome/home-assistant-voice-pe/home-assistant-voice.yaml"

esphome:
  name: ${name}
  friendly_name: ${friendly_name}

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

micro_wake_word:
  models:
    - model: /config/esphome/models/nee_maiku.json
      id: nee_maiku_model

api などの既存設定は消さずに残してください。

ESPHomeのバージョンに注意

公式パッケージが min_version: 2026.9.0 を要求していました。ESPHome Device Builder が 2026.8.1 だと次のエラーになります。

Current ESPHome Version is too old to use this package: 2026.8.1 < 2026.9.0.

アドオンを更新してから VALIDATE / INSTALL してください。

パスの注意

ESPHome Device Builder アドオンでは、ホスト側の /config/esphome/models/ をそのまま指定して問題ありませんでした。

model: /config/esphome/models/nee_maiku.json

/config/models/... に変換する必要はありませんでした。

再現性のために

github://esphome/home-assistant-voice-pe/home-assistant-voice.yaml は dev ブランチを参照します。記事執筆時点の内容を再現したい場合は、タグやコミットに固定するのがおすすめです。

10. 実機で調整

書き込み後、「ねえ、マイク」と話しかけて反応を確認します。

  • 反応しない → probability_cutoff を下げる(例: 0.85 → 0.75)
  • 誤検知が多い → probability_cutoff を上げる(例: 0.85 → 0.9)

YAML 側で上書きできます。

micro_wake_word:
  models:
    - model: /config/esphome/models/nee_maiku.json
      id: nee_maiku_model
      probability_cutoff: 0.8

ハマりどころまとめ

  • piper-sample-generator は pip 版ではなく GitHub clone を使う
  • ESPHome Device Builder のバージョンが古いとパッケージを読めない
  • モデルのパスは /config/esphome/models/... で通った
  • tensor_arena_size が小さいとモデルロードに失敗することがある
  • 合成音声1話者だけだと実環境での精度は未知数。自分の声を混ぜるのが重要

まとめ

日本語の wake word でも、Piper の日本語音声モデルと microWakeWord を組み合わせれば、Voice PE 用のモデルを作るところまで到達できました。現時点では、実機で「ねえ、マイク」を問題なく検出できています。

ただし、今回のモデルは次の制約があります。

  • TTS は1話者のみ
  • 自分の声は33件
  • 負例は英語中心
  • テストセットも小規模

実運用では、話者を増やす、日本語の負例を追加する、実環境で録音する、といった改善が必要です。

まずは「動くところまで」を作りたい人にとって、この手順が参考になれば幸いです。

参考リンク