前回でチャットは動くようになりました。今回は, 名前を呼んだら起動する部分を作ります。「M.I.Y.A.K.I.C.H.I.」と呼びかけたら聞く体勢に入る, あの動きです。

これはウェイクワードと呼ばれるもので, Siri や Alexa を呼び出すときのあの言葉と同じです。

定番は openWakeWord です。Colab のノートブックで1時間以内に自分のウェイクワードを学習できると案内されています。手順通りにやるだけだと思っていました。

一日かけて, 学習には一度もたどり着けませんでした。

想定していたことが3つとも外れていました。

順に書きます。

公式のノートブックが動かなかった

まず公式のノートブックを開きました。

結果として最終セルで落ちました。 出力されたはずの .onnx が見つからないという報告が GitHub の Discussion に複数上がっていて, 同じところで止まっていました。

パッチ版も Python 3.13 で止まった

対策を調べていたところ, 不具合に対応した「2026年版」のコミュニティ製ノートブックがありました。冒頭に「公式のノートブックは2023年から腐っている」と書かれていて, 事情はその通りでした。

しかしながら, こちらも止まりました。

ERROR: Could not find a version that satisfies the requirement piper-phonemize-cross (from versions: none)
ERROR: No matching distribution found for piper-phonemize-cross

from versions: none は「このPythonバージョン向けの配布物が1つも無い」という意味です。

バージョンの指定を変えても解決しませんでした。Google Colab の既定 Python が 3.13 になっていて, ノートブックが入れようとしている piper-phonemize-cross は 3.12 向けのフォークでした。

そこで発生するエラーを基に3つの策を講じました。

どれも「もう要らないものを入れようとしている」ことへの対処で, 本当に必要なら後のセルで同じエラーが出ます。

あわせて, ランタイムが CPU のままだったことにも気づきました。cuda: False のまま進めても学習は終わりません。

依存していたものが1年前に消えていた

ここで前提から崩れていることに気づきました。

piper-phonemize のリポジトリを開くと, 先頭にこう書かれていました。

2025年7月にアーカイブ済み。espeak-ng が piper の Python wheel に直接同梱されたのでこのプロジェクトはもう必要ない。開発は piper1-gpl に移った。

サンプル生成側も同じでした。piper-sample-generator は v3.0.0 で phonemize を piper 1.3.0 に移し, piper-phonemize を deprecated にしています。

ノートブックが依存している前提は, 1年以上前に無くなっていました。 「2026年版」と銘打ったものでも, そこは追えていませんでした。

日本語では学習できない設計だった

そして根本の問題にもたどり着きました。

openWakeWord は学習データを英語TTSで合成する設計で, プロジェクト自体が「学習データが英語TTSに依存しているため英語のみ対応」と明言しています。

データを用意すれば作れる, という話ではありませんでした。

回避策は2つ考えました。

一つ目, 英語音素で綴りを近似する。

「M.I.Y.A.K.I.C.H.I.」なら, 日本語の発音に寄せた mee yah kee chee のように書いて学習させます。ただしこれは, 英語TTSが読み上げた音と自分が日本語で言う音が一致することを祈る作業で, 精度は事前に読めません。

二つ目, 日本語TTSでサンプルを作る。

piper-sample-generator は v3 から Piper の音声 (.onnx) を直接使えるようになっています。しかし日本語の音声モデルを持っているのは本家ではなくフォークの piper-plus 側で, こちらは espeak-ng に依存しない独自の G2P を使っています。音素体系が違うので, 本家由来のモデルと互換性がありません。

結局, さっきと同じ種類の互換性問題に突っ込むことになります。

GPUを替えても速くならない

それでも英語音素での近似は残っています。ローカルで学習するなら何が要るのか, 先に調べました。4060Ti があるので Colab より速いはずだと思っていました。

逆でした。

openWakeWord の学習は, 凍結した特徴抽出器の上に小さな分類器を載せるだけです。1536→32→32→1 の4層で, 出力されるモデルは250KB以下しかありません。2026年版のノートブックには「A100でも速くならない。ボトルネックはGPUではなくネットワークとCPU」と書かれていました。

効くのはディスクと回線でした。 負例データとして ACAV100M の事前計算済み特徴量が約17GB, 背景ノイズ用に FMA small が約8GB。合わせて25GB前後を落とすことになります。

そしてこれが, Colab が向かない理由でもありました。セッションが切れるたびに25GBを落とし直すことになります。 一度落とせば2回目以降の試行は速いので, 学習をやるならローカルのほうが向いています。GPUの性能の話ではなく, ダウンロードを捨てずに済むかどうかの話でした。

英語の名前で一度通した

25GBを落とすのは後日にして, 先に全体を繋ぐことにしました。

openWakeWord には学習済みモデルが同梱されていて, download_models() で取れます。その中の hey_jarvis_v0.1 を使えば, 学習を一切せずに動きます。

やりたかったのはウェイクワードの精度ではありません。「ウェイクワード → 文字起こし → RAG → 音声合成」が1本に繋がるかの確認でした。英語の名前で妥協して全体を通してしまえば, 日本語化は後から差し替えられます。

一度動くものができたので, 以降の切り分けが楽になりました。おかしな挙動が出ても, 配管ではなく個々の部品を疑えばよくなります。

専用モデルをやめて, Whisperに聞かせた

今回の目的は J.A.R.V.I.S. ではなく M.I.Y.A.K.I.C.H.I. で動くようにすることです。

そこで, 日本語に戻すためにウェイクワードの検出そのものを置き換えました。

今回, 質問の文字起こし用に faster-whisper も入れています。前回はキーボードで打っていたので, 声で入れるならこれが要ります。

つまり専用モデルを作るのではなく, これに聞かせるのです。

やっていることはこうです。マイクを80msずつ読み, RMSで音量を見て無音のフレームは捨てる。音がしたら1.2秒ぶん録って Whisper にかける。文字起こしの結果に「みやきち」が入っていたら起動し, 入っていなければ捨てて待機に戻る。

名前の表記は M.I.Y.A.K.I.C.H.I. ですが, 判定しているのは文字起こしが返してくる「みやきち」という文字列です。

音量で門を作っているので, 喋っていない間は推論が走りません。日本語なので音素の近似も学習も要らず, 追加のライブラリも入れずに済みました。

なお, RMSを計算するときに16bitの録音データをそのまま二乗すると値が溢れます。float32に変換してから計算します。

語頭が切れて, 表記が揺れた

楽になった代わりに, 専用モデルでは起きない問題が2つ出ました。

語頭が切れます。

音を検知した瞬間から録ると「みやきち」の「み」が落ちる。そこで, 検知より前の音が必要なため, 無音のフレームも0.24秒ぶん deque に保持しておいて, 検知した時点で前から繋ぎました。

表記が揺れます。

同じ発話が みやきち / ミヤキチ / 宮吉 と書かれる。1つの表記で判定すると取りこぼすので, 候補を複数持って, どれか1つでも含まれていれば起動とみなしています。initial_prompt に「みやきち」を渡して, その表記に引っ張ってもいます。

どちらも, 音を直接見るモデルなら考えなくて済む問題です。文字に落としてから判定する方式の代償でした。

録音の切り方で二度つまずいた

ウェイクワードとは別に, そのあとの録音でつまずきました。

言い淀むと切り上げられます。 無音の長さで録音の終わりを判定していたのですが, 1段階で数えると考えている時間が「話し終わり」になります。話し出す前 (4秒まで黙って待つ) と話し出した後 (無音1.5秒で終了) で数え方を分けました。

自分の声でループもしました。 応答を喋っている間にマイクを開けたままにしていると, VOICEVOX の音声を自分で拾って延々と自分に話しかけ続ける。発話中はストリームを止めて, 再開直後は溜まった音を捨ててから待機に入ります。

後者は, 音声で入力して音声で出力する構成にして初めて出てくる問題でした。前回はキーボードで打っていたので起きませんでした。

VRAMに3GBの空きはなかった

前回, 生成中のVRAM使用量は 5891MB / 8188MB で, 約2.3GBの余裕が残ると書きました。

そこに Whisper は入りませんでした。large-v2 を float16 で載せると3GB前後を占めます。2.3GBの余裕に3GBは入りません。 medium に落として同居させました。

余裕があると書いた2.3GBは, 次に足すものの大きさを知る前の数字でした。

合言葉は音で決まる

反応は良かったです。ただし数字はまだ取っていないので, 体感の話として書きます。

良かった理由は3つあります。

短い語や日常語に似た語を選ぶと誤検出が増えます。専用モデルでも Whisper方式でも, ここは変わらないはずです。合言葉は後から変えると音韻の都合で作り直しになるので, 実装の前に声に出して選ぶものでした。

名前を先に決めていたのが, 結果的に効いていました。

動かしやすい方は鍛えにくい

今回わかった対比を書いておきます。まだ試していない話なので, 短く。

openWakeWord は動かすのが大変でしたが, 鍛える道が用意されています。ベースモデルの学習のほかに custom verifier model という話者フィルタがあって, 公式ドキュメントによると5分程度の手間で作れるそうです。ただしフィルタなので, 誤爆を減らす方向にしか効きません。拾えなかったものを拾えるようにはなりません。

Whisper方式は動かすのが楽でしたが, 学習で伸ばす余地がほとんどありません。自分の声を学習させるならファインチューニングになります。

動かしやすい方は鍛えにくく, 動かしにくい方には安い鍛え方が用意されています。 鍛える段階に入るなら, openWakeWord に戻ることになります。

わかったこと

測っていないこと

正直に残しておきます。

次にやること

ここまでは既製のモデルを繋いだだけで, 学習は一度もしていません。次から, 自分の声のデータを使う段階に入ります。

openWakeWord は消していません。諦めたのではなく保留しました。

何を作ろうとしているか

前回書いた通りです。家のデスクトップを本体にして, 外にいてもVPNで繋ぐ。耳につけたカメラとマイクとイヤホンを通して, こちらの言うことを聞いて答える。外に出ない閉じた網の中で完結させる。

名前を呼んで起動する部分は, その入口です。手で Enter を押して質問している限り, 歩きながら使う道具にはなりません。

ただ, 呼びかけてから聞く体勢に入るまでに間があります。Whisper を一度通してから判定する作りなので, 音を直接見る専用モデルより遅い。ここは測って数字を出します。前回の0.207秒/字に足し算されるものなので, 短く答えることと同じく要件の側の話になります。

常時低電力で待機する設計でもないので, 小さい機械には載りません。デスクトップ前提の割り切りで, 今は動くことを優先しました。