初めに
前回の記事では、日本語 full-duplex 対話モデル J-Moshi-ext (7.5B) に「あみたろ」の声だけを転移させました。今回はその上に「話し方」を上乗せします。
具体的には、汎用「お嬢様口調」(「わたくし」「〜ですわ / のよ / かしら」) を、前回の voice clone checkpoint (あみたろ声) に対して 2 段構成で追加学習しました。1 段目は text 側の transformer のみ、2 段目は全パラメータを対象にしています。
結果は以下のとおりです。
- 話し方 (お嬢様語尾) の学習は成功。text 側の分布が明確に shift した (perplexity 検証で +83-89% gain)
- あみたろ声はほぼそのまま保持され、話し方だけが変わった
- text 側の transformer だけを学習した軽量版 (全体の ~5% を触るだけ) で、全パラメータ学習と同水準の gain が得られた
デモ
moshi.server の gradio-tunnel 経由でブラウザから live 対話した様子です。
「声はあみたろ、語尾は〜ですわ / かしら」の応答が返ってくる状態を確認しました。
開発環境
今回の追加学習側のみ記載します。声を移した checkpoint (前回の voice clone) を base に使うところまでは前回記事と同一構成です。
| 項目 | 値 |
|---|---|
| GPU | A100 SXM4 80GB × 2 (追加学習) |
| OS | Ubuntu 22.04 |
| Python | 3.11 |
| PyTorch | 2.7.0+cu126 |
| DeepSpeed | 0.15.x (ZeRO-3, bf16, no offload) |
| Base checkpoint | 前回の voice clone checkpoint (ayousanz/phase1b-jmoshi-ft-2026-07-06、あみたろ声) |
| 学習用コーパス | 100 dialog / 800 turn の合成お嬢様対話 (JSONL) |
何をやったか
最終的な pipeline は 4 段構成です。
- 合成お嬢様対話コーパスの生成: 10 topic × 10 dialog = 100 dialog、平均 8 turn。text は Claude Sonnet で生成し、正規表現で「〜ですわ / のよ / ますの / でしょう / かしら」の語尾出現率 ≥ 90% を要求。
- 前回の Irodori-TTS (あみたろ Full FT) で 2 話者 stereo 音声化: Speaker A = お嬢様 (あみたろ声)、Speaker B = 執事役 (中立)。24 kHz stereo mix で約 40 分。
- 前回の voice clone checkpoint を base に、text 側の transformer だけを追加学習: 全体の ~5% のパラメータのみ trainable、5 epoch。以下「軽量版」。
- 同じ base・同じ corpus で全 7.5B パラメータを追加学習: 5 epoch。以下「フル版」。
Moshi は「何を話すか」を担う text 側の transformer (tempformer) と、「どう発音するか」を担う音響側 (depformer) の 2 段構造になっており、軽量版 (3) は音響側を完全に freeze することであみたろ声を保護しつつ、語尾 pattern だけを text 側に学習させる設計です。フル版 (4) は両者を joint に学習して一貫性を狙う版で、比較のため両方走らせました。
うまくいったこと
- 話し方 (お嬢様語尾) の学習: perplexity 検証で軽量版 +83%、フル版 +89% の gain。両者とも 10 prompt 中 7 個でお嬢様側を選好 (voice clone のみの状態は 6/10)。
- あみたろ声の保持: live 対話の聴取判定で、フル版でもあみたろ寄りの声色は失われていない。軽量版は音響側 freeze なので当然保持。
- 軽量版で十分だった: パラメータ ~5% しか触っていない軽量版が、フル版とほぼ同じ perplexity gain を出した。cost / 効果比では軽量版が優位。
うまくいかなかったこと
- 対話能力の劣化 (base 由来): live 対話では、user 発話への追随が遅れる / 応答が短くなる / 話題が浅くなるといった劣化が体感でかなり感じられる。ただしこれは今回の追加学習で新たに壊したわけではなく、base に使った前回の voice clone checkpoint の時点で既に対話能力が落ちていたものを、そのまま継承してしまった形。前回記事でも副作用として書いた「発音の明瞭度が base より劣化」と同じ layer の話で、Irodori-TTS で合成した対話コーパスの質 (発音精度・応答の自然さ) が上限を規定している。
副産物として得られた知見
- Base J-Moshi-ext だけでも「わたくしも」「紅茶を淹れました」の後に女性的継続を予測する分布を既に持っていた (お嬢様選好 60%)。日本語事前学習が想定より丁寧語文脈をカバーしている副産物。
- 稀語彙 (「〜ですわ」「わたくし」) は Irodori-TTS の学習分布外だが、事前の 10 sample × 3 語尾チェックでは 3 group 全 pass。Moshi 側の追加学習でも発音破綻は観測されなかった。
fine-tune までの手順
1) お嬢様対話コーパスの合成
前回の合成対話生成 pipeline (10 topic × 10 dialog = 100 dialog) をそのまま流用し、text 側だけをお嬢様語尾に書き換えます。1 dialog は Speaker A (お嬢様、amitaro voice) と Speaker B (執事役、中立) の 2 話者、平均 8 turn です。
{ "id": "cp-ojousama-mild-0101", "topic": "朝の挨拶", "turns": [ {"speaker": "A", "text": "ごきげんよう、田中。今日もよいお天気ですわね。"}, {"speaker": "B", "text": "おはようございます。お目覚めはいかがでしたか。"}, {"speaker": "A", "text": "よく眠れました。窓から鳥のさえずりが聞こえます。"}, {"speaker": "B", "text": "それは何よりです。朝食のご用意ができております。"}, {"speaker": "A", "text": "ありがとう。今日はパンにしようかしら。"}, ... ] }
対応する manifest.jsonl は前回と同構造で、turns[] に turn 単位の話者 / 時刻 / kanji / kana を持ちます。
2) 軽量版: text 側だけを追加学習
moshi-finetune の config を以下のようにします。
# configs/persona-tempformer.yaml model: base: ayousanz/phase1b-jmoshi-ft-2026-07-06 # 前回の voice clone checkpoint train: params_to_ft: tempformer # text 側だけ、音響側は freeze (~5% trainable) num_processes: 2 lr: 3e-5 ds_config: configs/zero3-bf16-nooffload.json per_device_batch_size: 1 grad_accum: 4 precision: bf16 epochs: 5 data: manifest: ojousama_mild_100/manifest.jsonl
Loss は total 0.49 (音響側 1.10、text 側 0.07) で 30 分程度でした。text 側の loss が非常に低く、text 側の transformer が語尾 pattern を pin-point で学習しているのが数字に出ています。
3) フル版: 全パラメータを追加学習
同じ corpus・同じ voice clone base に対して、今度は全パラメータを対象にします。
# configs/persona-full.yaml model: base: ayousanz/phase1b-jmoshi-ft-2026-07-06 train: params_to_ft: all # 全 7.5B ... # 他は軽量版と同一
Loss は total 1.10 (text 側 0.30)。軽量版の text loss (0.07) より高くなるのは、音響側も同時に学習するため text 側だけを一極集中させないためです。
4) checkpoint の後処理
前回同様、ZeRO-3 checkpoint を推論用 safetensors に変換します。今回 tools 側の torch.load に weights_only=True 前提の path があり、ZeroStageEnum の unpickle で落ちるので weights_only=False に patch する必要がありました。
# ZeRO-3 → fp32 safetensors → dep_q=8 変換 python -m deepspeed.utils.zero_to_fp32 ... python -m moshi.tools.clean_moshi ...
5) Perplexity で「学習できたか」を数値で確認
live 対話に進む前に、text 側だけの数値評価を挟みました。
10 個の同じ prompt に対して「中立語尾」と「お嬢様語尾」の 2 種の続きを用意し、学習後の model がどちらに高い確率を割り当てるかを測定します。両者の log 確率の差が正なら「お嬢様側を選好している」= 学習成功、負なら「中立側を選好」= 学習失敗 という判定です。
実装は Moshi の text stream に prompt + continuation を feed し、forward_text で得られる logits から continuation 部分の log 確率を積み上げるだけです。
# persona_perplexity.py (要約) prompt_ids = sp.encode(prompt) cont_ids = sp.encode(continuation) # Moshi は text と audio codebook を同時に入力する必要があるので # audio 側は zero token で埋め、text 側だけに実データを載せる codes = torch.full((1, K, T), zero_token_id, dtype=torch.long, device=device) codes[:, 0, :] = torch.tensor([prompt_ids + cont_ids]) with lm.streaming(1): _, text_logits = lm.forward_text(codes) log_probs = torch.log_softmax(text_logits[0, 0], dim=-1) total_logprob = sum(log_probs[len(prompt_ids)+i-1, tok].item() for i, tok in enumerate(cont_ids))
Test pair の例:
- 「今日はいい天気」 + 「ですね」 vs 「ですわ」
- 「行ってみましょう」 + 「か」 vs 「かしら」
- 「紅茶を淹れました」 + 「よ」 vs 「ますの」
結果:
| Checkpoint | お嬢様選好率 | log prob 合計差 | 追加学習の効果 |
|---|---|---|---|
| Voice clone のみ (前回、あみたろ声だけ入っている状態) | 6/10 (60%) | +6.48 | 基準 |
| 軽量版 (text 側だけ追加学習) | 7/10 (70%) | +11.86 | +5.38 (+83%) |
| フル版 (全パラメータ追加学習) | 7/10 (70%) | +12.26 | +5.78 (+89%) |
顕著な gain 例 (Δ = フル版 − Voice clone のみ):
- 「よくおできになった」 + 「でしょう」: -1.06 → +0.81 (逆転, +1.87 gain)
- 「今日はいい天気」 + 「ですわ」: -1.77 → -0.08 (ほぼ同等, +1.69 gain)
- 「それは素敵」 + 「ですわ」: -1.48 → -0.15 (ほぼ逆転, +1.33 gain)
Voice clone だけの状態でも既に 60% はお嬢様側を選好している ― これは J-Moshi-ext の大規模日本語事前学習の副産物と考えられます (「わたくしも」「紅茶を淹れました」等の丁寧文脈で女性的継続を予測する分布が pre-training に含まれている)。今回の追加学習はその上にお嬢様の語尾パターンを積み増した形です。
6) Live 検証 (moshi.server + gradio-tunnel)
学習した checkpoint を実 live 対話で確認します。今回は Mac ↔ vast.ai の直接 SSH port forwarding が過去に不安定だったため、moshi.server 内蔵の gradio-tunnel を使う経路にしました。
# vast.ai 側 (RTX 3090) pip install "moshi==0.1.0" "sphn<0.2" gradio # フル版 checkpoint を HF から取得 huggingface-cli download ayousanz/moshi-persona-stage3-ojousama-2026-07-06 \ model.safetensors moshi_lm_kwargs.json --local-dir /workspace/stage3 # gcc を install (torch.compile の triton kernel build に必要) apt-get install -y gcc g++ export CC=/usr/bin/gcc CXX=/usr/bin/g++ # moshi.server を gradio-tunnel 付きで起動 python -m moshi.server \ --host 0.0.0.0 --port 8998 \ --hf-repo nu-dialogue/j-moshi-ext \ --moshi-weight /workspace/stage3/model.safetensors \ --gradio-tunnel
数分後にログに Tunnel started, ... https://xxx.gradio.live が出るので、その URL を Mac / iPhone のブラウザで開き、マイク permission を許可して対話開始。声色はあみたろのまま、応答語尾に「〜ですわ / かしら / でしょう」が混ざる状態を確認できました。
詰まったポイントと解決
(a) sphn の API 変更で server が音声受信で crash する
moshi==0.1.0 は Opus 音声受信で sphn.OpusStreamReader.read_pcm() を呼びますが、sphn>=0.2.0 ではこの API が消えています。ブラウザ側から見ると WebSocket 接続後すぐに切断され、DevTools console に以下が延々出続けます。
index-XXX.js:425 WebSocket is already in CLOSING or CLOSED state. index-XXX.js:425 WebSocket is already in CLOSING or CLOSED state. ...
Server 側は表向き Running on http://0.0.0.0:8998 まで正常に見えますが、実接続で以下が発生します。
File ".../moshi/server.py", line 115, in opus_loop pcm = opus_reader.read_pcm() AttributeError: 'builtins.OpusStreamReader' object has no attribute 'read_pcm'
対処は sphn を 0.1.12 に downgrade するだけです。
pip install "sphn<0.2"
moshi==0.1.0 を使う限り必ず踏むので、実質的な requirement pin です。
(b) moshi_lm_kwargs.json は復元 side channel
moshi.server は checkpoint の .safetensors しか読まず、model architecture は moshi/models/loaders.py::_lm_kwargs に hardcode されています。fine-tuning で config を変えた場合、moshi_lm_kwargs.json を checkpoint と同じ dir に置いておくと後から可搬性が保てます。今回は base J-Moshi-ext と同一 architecture (dim=4096, num_layers=32, dep_q=8) なので trivial ですが、Depformer size を変える実験を後で走らせる場合は必須です。
評価結果
4 次元で見ます。
| 能力 | 素の J-Moshi-ext | 前回 (voice clone のみ) | 今回 (話し方も追加) | 判定 |
|---|---|---|---|---|
| テキスト生成の破綻の有無 | 正常 | 保持 | 保持 + お嬢様化 | 良化 |
| 声質 (target = あみたろ) | 汎用 Moshi 声 | あみたろ寄り | あみたろ寄り (保持) | 保持 |
| 対話能力 (turn-taking) | 動作 | 保持 | 保持 | 保持 |
| 話し方 (お嬢様語尾) | ほぼ中立 (60%) | ほぼ中立 (60%) | お嬢様選好 70% / +89% gain | 転移成功 |
軽量版とフル版の対比。
| 項目 | 軽量版 (text 側だけ) | フル版 (全パラメータ) |
|---|---|---|
| 学習対象のパラメータ割合 | ~5% | 100% (7.5B) |
| Loss (合計) | 0.49 | 1.10 |
| Loss (text 側のみ) | 0.07 | 0.30 |
| お嬢様選好率 | 7/10 | 7/10 |
| log prob gain (vs voice clone のみ) | +5.38 (+83%) | +5.78 (+89%) |
結論
前回 voice clone した J-Moshi-ext の上に、合成お嬢様対話 100 dialog を text 側だけの追加学習 (軽量版) または全パラメータ追加学習 (フル版) で上乗せすることで、あみたろの声を保持したまま、お嬢様の話し方だけを text 層に焼き込むことができました。Perplexity 検証で voice clone base から +83-89% の gain、live 対話でも語尾の shift を確認しています。Cost はコーパス生成 $2-3 + 学習 $5-15 + live check $0.5 程度、合計 $10-20 に収まりました。
参考
- 軽量版 (text 側だけ) checkpoint:
ayousanz/moshi-persona-stage2-ojousama-2026-07-06 - フル版 (全パラメータ) checkpoint:
ayousanz/moshi-persona-stage3-ojousama-2026-07-06 - 前回の voice clone checkpoint:
ayousanz/phase1b-jmoshi-ft-2026-07-06 - 学習リポジトリ:
ayutaz/moshi-finetune - J-Moshi 論文: nu-dialogue/j-moshi
- Moshi 論文: kyutai-labs/moshi


