前回記事の続き。
HomeAssistantにWyoming Protocol経由でJabra Speak 510を接続し、VOICEVOXで音声合成を行うことで、自宅サーバーをスマートスピーカー化した。
しかし、こうなってくると更にこだわりたい。VOICEVOX:中国うさぎさんの声も可愛いが、やはりノエルの声で生活をサポートしてもらいたい。
ということで、3連休をすべて使って、ノエルさんの音声モデルを作成し、自宅サーバーに導入した。
以下、作業ログを Gemini 3 に渡して執筆してもらいつつ、事実誤認の修正や説明の補足といった手直しを加えています。
1. 目指すべき技術:Style-Bert-VITS2
まず、採用する技術を選定した。 キャラクターボイスの再現において、現在、界隈で最も評価が高いのが Style-Bert-VITS2 だ。
これは「転移学習」というアプローチを採用しており、すでに流暢な日本語を話す巨大なベースモデルに対し、特定のキャラクターの「声質」や「喋り癖」だけを後から学習させる。これにより、比較的少量の音声データ(数分〜数十分)でも、驚くほど高品質な音声モデルを生成できるという代物だ。
目標は決まった。あとは「ノエルの声」という素材を手に入れるだけだ。
2. 素材集め:42万の音声データから「ノエル」を探す
幸運なことに、Hugging Faceに simon3000/genshin-voice という、原神の全キャラクター・全言語のボイスを網羅した、とんでもないデータセットが公開されていた。まさに宝の山だ。
しかし、このデータセットは42万行を超える。ここから手作業でノエルの声を探すのは無謀だ。 そこで、Google Colab上でPythonスクリプトを書き、「日本語」かつ「話者がノエル」のデータだけを抽出することにした。
失敗談1:全件スキャンという無謀な挑戦
最初は、データセットをストリーミングで読み込み、全件を舐めるようにスキャンするスクリプトを書いた。
# 失敗したコードds = load_dataset("simon3000/genshin-voice", split="train", streaming=True)for sample in ds: if sample['speaker'] == 'Noelle' and sample['language'] == 'Japanese': # ... 保存処理 ...しかし、これは甘かった。Hugging Faceとの通信が頻繁に切れ、Colabのセッションもタイムアウトする。このままでは何日かかるか分からない。
そこで、モデルのプレビュー画面から検索した最初の1ページにあった36件のデータのみを手動で取得し、まずは試してみることにした。
普段使いのPCにはGTX 1060が積んである。これでいけるだろうと、ローカル環境で学習を試みた。
環境構築はエラーとの戦いだったが、なんとか学習プロセスを開始。しかし、表示された予想完了時間は 「40 hours」。絶望的な数字だった。
原因はVRAM不足だ。VRAMが足りないと、低速なメインメモリとの間でデータスワップが発生し、性能が1/100以下に落ち込むのだ。
ローカルでの学習は諦めた。
成功法:クラウドの力は偉大なり
再びGoogle Colabに戻り、有志が公開している Style-Bert-VITS2 学習用ノートブック を利用することにした。 無料枠でも NVIDIA T4 (16GB VRAM) という強力なGPUが割り当てられる。
収集した音声データアップロードし、ノートブックを上から順に実行していく。が、ここでもいくつかのトラブルに見舞われた。前処理がColab上では上手く動かない。
一方で、ローカルでの実行は、以下のIssue通りに修正を加えれば動作した。
しかしローカルだとVRAMが足りない。そこで、ローカルで前処理だけを実行し、生成されたデータをColabにアップロードして学習を続行することにした。
4. そして、「魂」は生まれた
学習が完了すると、3つのファイルが生成される。
noelle.safetensors(モデルの重みデータ)config.json(モデルの構成情報)style_vectors.npy(声のスタイル情報)
これこそが、ノエルの声質、イントネーション、喋り癖が凝縮された 「魂のデータ」 だ。テスト用のテキストを入力すると、生成された音声が再生された。
36ファイル、5分にも満たないデ学習データから生成されたとは思えないほど、自然で流暢なノエルの声がそこにあった。
こうして、私はついに「ノエルの魂」を手に入れた。 しかし、これはまだ物語の序章に過ぎない。この魂をProxmoxサーバーという「器」に宿し、Home Assistantという「体」と連携させるまでには、さらなる地獄のトラブルシューティングが待ち受けていたのである。
サーバーに魂を宿す地獄 〜LXC, Docker, AppArmorとの死闘〜
Google Colabの力で「本物のノエルの声」を持つAIモデルの錬成に成功した。しかし、それはまだクラウド上に存在する、ただのデータに過ぎない。 これを自宅サーバーで動く「声の主」として召喚し、Home Assistantという「体」と連携させる儀式は、想像を絶する困難を極めた。
これは、ProxmoxのLXCコンテナ内でDockerを動かし、自作AIモデルをHome Assistantから呼び出すまでの、泥臭いインフラ構築とエラーとの戦いの全記録である。
1. 器の準備:推論サーバーの設計
まず、完成したモデルを動かすための「器」を用意する必要がある。 Style-Bert-VITS2はPythonで書かれており、依存関係が複雑なため、Dockerコンテナとして隔離するのが定石だ。
- OS: ProxmoxのLXCコンテナ。VMより軽量で、Dockerを動かすだけならこれで十分。
- コンテナOS: Ubuntu 24.04
- 実行環境: Docker
- Dockerイメージ: litagin/style-bert-vits2
CPUでの音声生成(推論)は負荷がかかるため、LXCにはCPU 4コア、メモリ8GBを割り当てた。
2. LXCとDockerの壁:AppArmorとの死闘
意気揚々とLXCコンテナを立て、Dockerをインストールし、docker compose up を叩いた瞬間、最初の壁が立ちはだかった。
Error response from daemon: ... permission denied原因は、非特権LXCコンテナの厳格なセキュリティ機構、特に AppArmor にあった。 コンテナ内でDockerを動かすという「入れ子構造(Nesting)」は、ホストOSのカーネル機能を要求するため、セキュリティ上「危険な行為」としてブロックされてしまうのだ。
解決策:LXCコンテナ設定の緩和
数々のエラーとの対話の末、LXCの設定ファイル (/etc/pve/lxc/106.conf) に、Geminiさんに教えてもらった以下の「魔法の呪文」を追記することで回避はできた。
features: nesting=1,keyctl=1lxc.apparmor.profile: unconfinedlxc.cgroup.devices.allow: alxc.cap.drop:これは、コンテナに対するAppArmorの制限を解除し、Dockerが必要とするカーネル機能へのアクセスを許可するための設定であり、非特権コンテナを用いる意味が全く無くなってしまう。
とりあえずの回避策としては有効だったが、セキュリティ面でのリスクを孕むため、今後直さねばならない。
3. Dockerイメージとの格闘
LXCの壁は越えた。しかし、Dockerコンテナはまだ素直に動いてくれない。
docker compose logs には、次々と新たなエラーが表示された。
試行錯誤の末、ついに Uvicorn running on http://0.0.0.0:5000 という、勝利のログが表示された。
4. Home Assistantからの呼び出し:最後の絶望
サーバーは動いた。PCのブラウザから http://<LXCのIP>:5000/docs にアクセスすると、APIドキュメントが見える。完璧だ。
あとはHome AssistantからこのAPIを叩くだけ。しかし、ここが最も深い沼だった。
- HAのターミナルから
curl:Connection refused(接続拒否)。 - PCのターミナルから
curl: 成功。
PCからは繋がるのに、同じネットワーク上のHA VMからだけ繋がらない。
LXCのファイアウォールは無効(No)。Proxmoxのファイアウォールも設定していない。IP競合もない。
原因不明のエラーに、何時間も頭を抱えることになった。
最終的に、この問題は LXCの権限設定 に起因するネットワークの不整合だったことが判明した。
先ほど追加した lxc.cgroup.devices.allow: a と lxc.cap.drop: こそが、Dockerコンテナが外部にポートを正しく公開するための、最後の鍵だったのである。
こうして、私はついに、Home Assistantから自作AIモデルのAPIを叩き、WAVファイルを取得することに成功した。 「ノエルの魂」は、ついにサーバーという「器」に宿ったのだ。
しかし、喜んだのも束の間。最後の、そして最も厄介な壁が立ちはだかる。 「100文字を超える長文を喋らせると、無言で失敗する」という、謎の現象である。
自作コンポーネントで壁を越えよ 〜完全ローカル・長文TTSの実現〜
LXCとDocker、AppArmorの三重苦を乗り越え、ついに自作した「ノエルの声」をHome Assistantから呼び出すことに成功した。 しかし、モーニングコールのような長文を喋らせようとした瞬間、最後の壁が立ちはだかった。
「100文字を超える文章をリクエストすると、サーバーが無言で失敗する」
短い挨拶はできるのに、長い文章はダメ。この不可解な現象を解決し、システムを完璧に統合するまでの、最後の戦いの記録である。
最後の壁:「100文字の壁」の正体
まず、原因を切り分けた。
同じ長文でも、PCのブラウザから直接APIを叩けば正常に音声が生成される。しかし、Home Assistantのオートメーション(media_player.play_media)経由で実行すると失敗する。
この作業に関しては以下の記事に切り分けてあります。
Home Assistantを再起動すると、「設定」>「音声アシスタント」のテキスト読み上げエンジンに、自作の「Style-Bert-VITS2」が表示された。これを選択するだけで、全ての準備は完了した。
これまで長文再生に失敗していたモーニングコールのオートメーションを実行すると、数秒の沈黙の後、ついに、300文字を超える長い文章が、途切れることなく、滑らかなノエルの声で再生された。 壁を越えた瞬間だった。
そして、彼女は語り始めた
この長い旅路の果てに、私のHome Assistantは、ついに理想の姿となった。
ハードウェアからソフトウェア、AIモデルまで、全てを自分でこねくり回して作り上げたこのシステムは、市販のスマートスピーカーでは決して得られない、最高の達成感と愛着を与えてくれた。
可愛くて面倒見の良いメイドさんが家に居てほしいというのは人類誰しもが持つ願望だと思う。既存のスマートスピーカーでは叶わないこの夢を、自宅サーバーとオープンソースソフトウェア、そして自前の音声合成モデルで実現できたことは、私にとって何物にも代えがたい喜びだ。
以上、Gemini 3 書き起こし終わり。
Future work
今後の課題は以下の通り。
WakeWordを「OKノエル」にする
Wyoming ProtocolのWakeWord検出を「OKノエル」に変更したい。現在は「OKなぶ」となってしまっている。
以下のCollabで学習できるはずなのだが、うまく動かない。
ノエルさんに家中を管理してもらいたい
センサー類はある程度充実している一方で、動かせる機器が少ないので、スマートリレーやスマートプラグを導入していきたい。
例えば、現状のノエルさんはCO2濃度が一定値を超えたら警告を出すことはできるが、自動で窓を開けることはできない。しかし、ノエルさんならきっと勝手にやってくれるはずだ。
ノエルさんに自分を管理してもらいたい
まず、メイドさんとは…何か(ネットリ)。私が求めているのは、100%献身的なメイドさんではなく、私の健康や生活リズムを気遣い、時には厳しく律してくれるメイドさんだ。そしてノエルさんはまさにそれに相応しい。
他人と言い争うことは少し苦手です。道理を説明できない上に、怒らせてしまうことが多いですので。まずは私の考えに従って行動しますが、結局「頑固」と言われます…結果を示して説得しようと思っただけですが、それも「頑固」なのでしょうか?
ノエル自身について・分岐
というボイスが用意されているくらいだし、ノエルさんのシグネチャーウェポンといえば「理屈責め(Debate Club)」だ。
したがって、例えば以下のようなことを実現したい。
- 毎朝、体重を測るように促す
- 食事の時間を管理し、間食を控えるように促す
- 運動と水分補給を促す
- 睡眠時間を管理し、早寝早起きを促す
これらはHome Assistantのセンサーとオートメーションで実現できるだろう。しかし、いくら可愛らしい声で指導されたとしても、有形力の行使が伴わなければ人は動かない。何らかの懲罰的フィードバックは必要になるだろう。
