ALL NOTES

Style-Bert-VITS2で自分の声を学習する:Colabで止まる5か所とJP-Extra

自分の声を100文録音し、Style-Bert-VITS2 JP-ExtraをGoogle ColabのT4で学習して、Macでナレーションを合成しました。最新のColabで公式ノートが止まる5か所の直し方、Macで動かすまでの詰まり、AIの声から替えて尺が約2/3になった実測まで書きます。

はじめに

YouTube の解説チャンネル「まどろみ研究所」では、動画を Markdown の脚本から自動で作っています。
仕組みの全体は、Remotionを使わずに動画を自動生成する:Next.js+Playwright+ffmpeg に書きました。

この記事は、その仕組みのうちナレーションの話です。

最初の60本ほどは、OpenAI の音声合成(gpt-4o-mini-tts)で読ませていました。
途中から、自分の声を学習させた音声合成モデルに替えています。使ったのは、日本語に強いオープンソースの音声合成 Style-Bert-VITS2 の、日本語に特化した JP-Extra 版です。

この記事で書くことです。

  • 学習用の声を、ブラウザで100文録る
  • Google Colab の T4 で学習する(8,100ステップ・3時間5分)
  • 最新の Colab では、公式のノートが5か所で止まる。その直し方
  • Mac で合成するまでに踏んだこと
  • AI の声から替えたら、同じ脚本でナレーションが約2/3の長さになった話
  • 読み違いをどう直しているか

なお、この記事に声のサンプルは載せていません。違いは、測った数字で書きます。

⚠️ Colab の環境は、2026年9月時点のもの(Python 3.13・torch 2.11)です。Colab が更新されると、別の所で止まるかもしれません。


1. 全体の流れ

段 やること 使うもの
1 学習用の声を録る 自作の録音ページ(ブラウザ)
2 学習データを整える 自作のスクリプト(音量・端の処理)
3 Google ドライブへ上げる rclone
4 Colab で学習する 公式ノートを直したもの(T4 GPU)
5 学習したモデルを Mac へ持ってくる rclone
6 学習の段階ごとに聞き比べる 自作の合成スクリプト
7 動画のナレーションに使う 動画を作る仕組みに組み込む

学習は Colab で行います。学習には NVIDIA の GPU が要るので、Mac ではできません。
一方、学習が済んだモデルで読み上げるのは、Mac でも十分速くできます(6章)。


2. 学習用の声を録る

何を読むか

読む文は、これまでの動画の台詞から選びました。動画1本から5文ずつ、始め・中ほど・終わりが混ざるように拾っています。

  • 14〜56文字に絞る。 公式のノートの目安は「1ファイル2〜12秒」で、台詞は1秒あたり約5文字のため
  • 英字を含む文は外す。 読み方と書き起こしがずれやすいため

これで270文の一覧ができ、そのうち最初の100文を録って学習しました。

録音のページを自作した

録音は、ブラウザで動く小さなページを自作して行いました。手で録って切り分けるのは大変なので、次のことを自動にしています。

決めたこと 理由
読み終えて 1.5秒 黙ると、自動で止めて保存し、次の文へ進む 止めるためにキーを押すと、キーの音が入る。最初は1秒にしたら、文の途中の間で止まってしまった
前後の無音を切る(前は0.12秒・後ろは0.2秒だけ残す) 学習に要らない無音を入れない
音が割れた録音は保存しない 学習に入れると声が崩れる
保存のたびに「声と背景の差」を出す(目安は30dB以上) 足りないと、背景の音まで声として覚える
他の文より極端に速い・遅い文は止めて知らせる 速すぎるのは途中で切れた、遅すぎるのは言い直した疑い
ブラウザの声の加工(エコー除去・雑音除去・自動音量)を切る 学習には生の声が要る

最後の点は見落としやすいところです。ブラウザでマイクを使うと、既定でこれらの加工が入ります。getUserMedia で、明示的に切っておきます。

const stream = await navigator.mediaDevices.getUserMedia({
  audio: {
    channelCount: 1,
    echoCancellation: false,
    noiseSuppression: false,
    autoGainControl: false,
  },
});

録音は 48kHz・モノラル・16bit の wav で保存しています。


3. 学習データを整える

録った wav はそのまま使わず、複製を作って整えてから上げます。

  • 音量をそろえる。 話している所を −23dBFS に合わせる(いちばん大きい所が −1dBFS を超えないように上げ幅を抑える)
  • 終わりを 40ms かけて絞る。 「〜ます」「〜です」の息の音が消えきる前にファイルが終わっていた文が、100本中14本あった。そのままだと、学習した声の終わりも不自然になる
  • 頭も 10ms 絞る。 切り口の「プツッ」を消すため

書き起こしは、ファイル名・話者名・言語・台詞を | でつないだ esd.list に書きます(例)。

0001.wav|my_voice|JP|電車は、ただレールの上に乗っているだけです
0002.wav|my_voice|JP|…

台詞は自分が読んだ文そのものなので、音声認識で書き起こす必要はありません。読む文を先に決めて録る形にしたのは、これが理由でもあります。


4. Google Colab で学習する

公式のノート(colab.ipynb、ver 2.7.0)を、次の章の5つの直しを入れた版にしてから使いました。

  1. Colab で「ファイル → ノートブックをアップロード」から、直した版のノートを開く
  2. 「ランタイム → ランタイムのタイプを変更」で T4 GPU にする
  3. セルを上から流す(環境構築 → ドライブ接続 → 初期設定 → 前処理 → 学習)
  4. 待つ。1,000ステップごとにドライブへ保存される
  5. 終わったら「ランタイム → ランタイムを接続解除して削除」

最後の5は忘れやすいので注意です。つないだままだと、コンピューティング ユニットを使い続けます。
学習中は、タブを閉じたり Mac をスリープさせたりしないようにします。もし切れても、途中経過がドライブに残っているので、前処理を飛ばして学習から再開できます。

どれくらい回すか

文の数 周(エポック) ステップ 時間(T4)
100 300 8,100 3時間5分

1周のステップ数は、文の数 ÷ 4 より少し多くなります(100文で27)。

ただ、結果からいうと 6,000ステップ程度で十分でした(7章)。次からは約6,000ステップで止めるつもりです。


5. 最新の Colab で、公式ノートが止まる5か所

ここがこの記事でいちばん書きたかったところです。

2026年9月時点の Colab は Python 3.13・torch 2.11 になっていて、公式のノートが前提にしている版と噛み合いません。そのまま流すと、環境構築から学習の開始までの間に、5か所で止まりました。

症状 原因 直し方
Failed to build pyopenjtalk-dict / CMake < 3.5 has been removed CMake 4 が古い書き方を拒む 環境変数 CMAKE_POLICY_VERSION_MINIMUM=3.5
torchaudio has no attribute AudioMetaData / list_audio_backends 話者のスタイルを計算する pyannote が、torchaudio 2.9 以降で動かない torch・torchaudio・torchvision を 2.8.0・2.8.0・0.23.0 に下げる
hf_hub_download() got an unexpected keyword argument 'use_auth_token' pyannote が、今の huggingface_hub に無い引数を渡している pyannote の中の use_auth_token を token に置き換える
WeightsUnpickler error: Unsupported global torch 2.6 以降、torch.load の既定が安全側(weights_only=True)になった style_gen.py に限って weights_only=False にする
学習の開始直後に ml_dtypes has no attribute int2 TensorFlow が、ノートの入れた numpy・ml_dtypes の版と合わない TensorFlow を外す(学習には使わない)

5つとも、ノートの最初の「0. 環境構築」のセルに入れてしまえば、あとは公式の手順どおりに流せます。実際に使ったセルはこうです。

import os

os.environ["PATH"] += ":/root/.cargo/bin"
# 直し1:CMake 4 で pyopenjtalk-dict のビルドが落ちる
os.environ["CMAKE_POLICY_VERSION_MINIMUM"] = "3.5"

!curl -LsSf https://astral.sh/uv/install.sh | sh
# 本体は、学習に使った版に固定する(2026-09-23 時点の master)
!git clone -q https://github.com/litagin02/Style-Bert-VITS2.git; git -C Style-Bert-VITS2 checkout -q 66de777e06392c0f313600be03c43ef96658b244
%cd Style-Bert-VITS2/
!uv pip install --system -r requirements-colab.txt --no-progress
!python initialize.py --skip_default_models

# 直し2:pyannote が torchaudio 2.9 以降で動かない。torch 一式を 2.8 に下げる
!pip install -q torch==2.8.0 torchaudio==2.8.0 torchvision==0.23.0 --index-url https://download.pytorch.org/whl/cu128
# 直し3:pyannote が、今の huggingface_hub に無い引数(use_auth_token)を渡している
!grep -rl use_auth_token /usr/local/lib/python3.*/dist-packages/pyannote/ | xargs -r sed -i 's/use_auth_token/token/g'
# 直し4:torch 2.6 以降の weights_only で話者のモデルが読めない。style_gen.py に限って緩める
!grep -q "_t.load=lambda" style_gen.py || sed -i '1i import torch as _t; _o=_t.load; _t.load=lambda *a, **k: _o(*a, **{**k, "weights_only": False})' style_gen.py
# 直し5:TensorFlow が numpy・ml_dtypes と合わない。学習には使わないので外す
!pip uninstall -y -q tensorflow tf-keras tensorflow-text

exit()

最後の exit() は公式のノートにもともとあるもので、「セッションがクラッシュしました」と出ますが正常です。ランタイムを1回再起動させて、入れ直した版を読ませるためのものです。

直すときの考え方

  • 本体の版を固定する。 上のセルでは、Style-Bert-VITS2 本体を学習に使ったコミット(66de777)に固定しています。新しい版を取ってくると、直しが合わなくなることがあります
  • 止まった段階を見る。 前処理は5段階(初期化・リサンプル・テキスト・BERT 特徴・スタイル)に分かれています。ログの Step N と最後の例外を見れば、どこで止まったかが分かります
  • 通ったかの確かめ方。 前処理の出力の最後が (True, 'Success: 全ての前処理が完了しました。…') なら通っています

Colab がまた新しくなれば、別の所で止まる可能性があります。そのときも、この5つと同じく「何の版が、何と噛み合っていないか」を見れば直せるはずです。


6. Mac で読み上げる

学習したモデルは、1段階あたり約250MB です。ドライブから全部持ってくるのではなく、使う段階のファイル(*_s8100.safetensors など)と、config.json・style_vectors.npy だけを持ってきます。

Mac で動かすまでに踏んだ4つ

Mac には pip で style-bert-vits2 を入れて使います。ここでも4か所で止まりました。

症状 原因 直し方
pyopenjtalk-dict を入れるとビルドが始まる Mac 用のビルド済みのものが Python 3.12 までしかない Python 3.12 の venv を使う
No module named 'pkg_resources' Python 3.12 の venv には setuptools が入らない。81 以降は pkg_resources が無い setuptools<81 を入れる
numpy.dtype size changed ... Expected 96 from C header, got 88 pyopenjtalk-dict は numpy 1 系向けに作られている numpy<2 を入れる
Input type (c10::Half) and bias type (float) should be the same transformers 5 は、日本語の BERT モデルを保存時の型(fp16)のまま読む 読み込んだ BERT に .float() をかける

入れるときは、まとめてこうなります。

/opt/homebrew/bin/python3.12 -m venv .venv
.venv/bin/pip install style-bert-vits2 "setuptools<81" "numpy<2"

合成するコード

読み込みと合成の中心は、これだけです。

import os
os.environ.setdefault("PYTORCH_ENABLE_MPS_FALLBACK", "1")  # MPS に無い演算は CPU に回す

from style_bert_vits2.constants import Languages
from style_bert_vits2.nlp import bert_models
from style_bert_vits2.tts_model import TTSModel

BERT = "ku-nlp/deberta-v2-large-japanese-char-wwm"

bert = bert_models.load_model(Languages.JP, BERT)
bert.float()  # fp16 のまま読まれるので、声のモデル(fp32)に合わせる
bert_models.load_tokenizer(Languages.JP, BERT)

model = TTSModel(
    model_path="model/my_voice/my_voice_e300_s8100.safetensors",
    config_path="model/my_voice/config.json",
    style_vec_path="model/my_voice/style_vectors.npy",
    device="mps",
)
model.load()

sr, audio = model.infer(text="電車は、ただレールの上に乗っているだけです", length=1.0)

M5 の Mac(MPS)で、1秒の声を作るのに約0.08秒でした。最初の1回だけ、準備に数秒かかります。
初回は日本語の BERT モデル(約1.2GB)のダウンロードも走ります。

音量は、まとめて1回だけ持ち上げさせる

1つ気をつけたのが、infer が呼ぶたびに、いちばん大きい所を 0dBFS まで持ち上げることです。
読点で区切って1つずつ呼ぶと、区切りごとに音量がばらつきます。

そこで、読点の間は infer の改行での分割(line_split=True と split_interval)に任せ、自分で分けるのは文の境目だけにしました。持ち上げが文ごとに1回で済み、音量がそろいます。


7. 学習の段階を聞き比べる

4,000・6,000・8,100 ステップのモデルで同じ文を読ませて、聞き比べるページを作りました。

結果は、聞き分けられませんでした。
測ってみると、6,000 と 8,100 はほぼ同じで、4,000 だけ声の高さが録音より低い(130Hz に対して 112Hz)、という違いでした。

そのため、次からは約6,000ステップで止めることにしました。T4 で2時間強の計算です。


8. AI の声から替えたら、ナレーションが約2/3になった

同じ脚本の6文を、自分の声と OpenAI の声で読ませて測りました。この6文は録音に入っていない、初めて読む文です。

話す速さ 文中の間 前後の無音 6文の長さ 声の高さ
録音(学習に使った100文) 8.3モーラ/秒 - - - 130Hz
自分の声(8,100ステップ) 8.4モーラ/秒 1.7秒 1.1秒 25.2秒 130Hz
OpenAI の声 7.5モーラ/秒 9.3秒 4.8秒 39.0秒 117Hz

自分の声のモデルは、録音の速さと高さを、ほぼそのまま引き継いでいます。

そして、声だけを替えると、ナレーションは約3分の2の長さになりました。 1本まるごと読ませると、451秒が299秒(66%)です。
縮んだのは、ほとんどが間でした。話す速さの違いは1割ほどで、残りは文中の間と前後の無音です。

読み方を選ぶ

間が少なすぎても聞きづらいので、読み方を4通り作って聞き比べました。

読み方 length 読点のあとの間 文と文の間 1本の長さ
自然な速さ 1.0 0 0 4分59秒
ゆっくり 1.15 0 0 5分33秒
間を足す 1.0 0.3秒 0.6秒 6分06秒
ゆっくり+間 1.15 0.3秒 0.6秒 6分44秒
(OpenAI の声) - - - 7分31秒

length は話す速さで、大きいほどゆっくりになります。モデルが音の長さを伸ばすので、声の高さは変わりません。

選んだのは「間を足す」です。「ゆっくり+間」は、ゆっくり過ぎてじれったく感じました。


9. 動画を作る仕組みに組み込む

動画を作る仕組み(前の記事)には、こう組み込みました。

  • 脚本の頭に voice: self と書いた回だけ、自分の声で読む。それ以外は OpenAI の声のまま
  • 合成した wav は、台詞と読み方の設定のハッシュをファイル名にして保存する。台詞を変えた所だけ作り直し、変えていない所は前の音声を使う
  • 音量は 0.88 倍して、話している所の大きさを OpenAI の声にそろえる(BGM との釣り合いを変えないため)

ハッシュで保存しているのは、決定性のためでもあります。音声合成は、同じ文でも合成するたびに長さが少しぶれます。一度作った音声を素材として固定しておけば、撮り直しても声は変わりません。

1本ぶん(60行)を通しで合成して確かめると、文ごとの読む速さのばらつきは、OpenAI の声の 17.0% から 3.9% に減っていました。


10. 読み違いをどう直すか

自分の声のモデルは、自然に読めている一方で、読み違いはあります。いくつか例を挙げます。

書いた字 読まれ方 正しい読み
暮らしで、人はどれくらい にんはどれくらい ひとは
100% ひゃく(パーセントが消える) ひゃくパーセント
40〜60時間 ヨンジュウウロクジュウジカン よんじゅっからろくじゅうじかん
来月の1日 らいげつのイチニチ ついたち
23万件 にじゅうさんまんゲン にじゅうさんまんけん

学習し直さなくても、読みは直せます。いまは、読み違えやすい語に脚本でルビ(読みがな)を振り、ルビと違う読み方をする語だけを、かなにして声に渡しています。

  • 全部をかなにすると、アクセント(高低)が変わってしまう
  • 全部を漢字のままにすると、上の表のように読み違える

合成の前に、モデルに渡る読み(カタカナ)をルビと突き合わせれば、読み違いの多くは機械的に見つかります。
ただし、それでも見つからないものがありました。「静電容量」を「しずかでんようりょう」と読んだのは、合成の前の読みは正しかったのに、声にすると違っていた例です。これは耳で聞いて見つけて、かなで渡すようにして直しました。


おわりに

自分の声に替えてよかったのは、声そのものより、間でした。
AI の声は丁寧に間を取るぶん、同じ脚本でも長くなります。自分の声のモデルは、録音したときの自分の話し方をそのまま引き継ぐので、間の取り方も自分のものになりました。

Style-Bert-VITS2 の学習は、100文・T4 で3時間ほどです。手間がかかったのは、学習よりも、Colab と Mac で環境を通すところでした。同じところで止まっている人の役に立てばうれしいです。

この連載では、ほかに次の話を書いていく予定です。

  • 脚本を書くところから撮影までを、Claude Code に任せている話
  • 3D の回を、Godot でコードだけで撮っている話

動画を作る仕組みの全体は、Remotionを使わずに動画を自動生成する にあります。
作った動画は「まどろみ研究所」で公開しています。