0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

日本語TTSの読みは辞書で全部潰せない。3層で作った話

0
Posted at

TL;DR

  • 解説動画の読み上げ品質を上げるために、最初はユーザー辞書に語を登録していました。辞書で潰せないものが3種類あります
  • 潰せないもの1: 複合語での再発。単独では正しく読めるのに、長い複合語に入ると別の読みになる
  • 潰せないもの2: 記号混じりのLatin語。トークナイザが分割するので語として一致せず、辞書に登録しても無視される
  • 潰せないもの3: 文脈依存の同形異音語(方=かた/ほう)。辞書で片方に固定すると、もう片方の用法が壊れる
  • 結果、読み品質を3層で作りました。語単位辞書 / アクセント推定 / 文脈依存の文単位オーバーライド
  • 人手の正解読み表を丸ごと辞書に入れてはいけません。96語のうちズレていたのは6語で、残り90語を上書きすると正しい読みを壊します
  • そしてアクセント(高低)はカナからは見えないので、最後は音声を生成して耳で確定します

観測期間: 2026-07 〜 2026-08


はじめに

スライドと原稿から解説動画を自動生成しています。音声合成にはVOICEVOX(四国めたん、エンジン0.25.2)を使っています。

日本語の音声合成は、普通の文はかなり自然に読みます。壊れるのは固有名詞と専門用語です。動画は人に見せるものなので、社名や人名を間違えたまま公開するわけにはいきません。

最初はユーザー辞書に登録していけばよいと思っていました。実際にはそれでは足りませんでした。この記事は、何が辞書で潰せて何が潰せないか、という整理です。

なお、この記事は「LLMに読みを判定させたら規模で壊れた」という別の記事の前提になっています。3層のうち3層目がその工程です。

1. まずエンジンのバージョンを上げる

品質の話をする前に、これが一番効きました。

古いバージョン(2021年のもの)を使っていて、不自然さの主因がそれでした。0.25.2に上げたら別物になりました。チューニングの前にバージョンを確認する のは、当たり前ですが書いておきます。

あわせて実運用のための設定です。

  • GUIを出さずにヘッドレス起動する(エンジンの実行ファイルにホストとポートを指定する)
  • 未起動だとポートが無応答になるので、ビルド前に起動確認を入れる

2. 文単位で合成する

長文を一括で合成すると、のっぺりします。文ごとに合成して結合すると、韻律がリセットされて自然になります。

確定した構成はこれです。

文分割(。!?)して合成
+ intonationScale = 1.2
+ 文末ポーズ 0.25 秒

ここで1つバグを踏みました。

「…使われるの?」です
  → ? で文が割れる
  → 「です」が単独で読まれる

? で分割すると、閉じ括弧の後の「です」が孤立します。閉じ括弧の直前では分割しない、という規則に直しました。日本語の文分割は、句点や疑問符だけを見ていると括弧で破れます。

3. 第1層: 語単位の辞書

固有名詞と熟語の訓読み誤りは、ユーザー辞書で潰せます。

発見例を1つ。

利益相反 → リエキ・アイハン
("相反" を訓読みしてしまう。正しくは リエキ・ソウハン)

これは辞書に登録して解決します。登録時のパラメータで効くのは2つです。

  • アクセント核の位置(0を指定すると平板になる)
  • 優先度(長い複合語を優先させる)

落とし穴: 複合語での再発

一番驚いたのがこれです。

「七夕」を単独で読ませる → タナバタ(正しい)
「株式会社七夕研究所」を読ませる → カブシキガイシャ・ナナユウ・ケンキュウショ

単独では正しく読める語が、長い複合語に入ると別の読みで再発します。 トークナイザが複合語を別の区切りで解析して、別の読みを当てるからです。

対策は、複合語ごと登録することです。「七夕」を直しても「株式会社七夕研究所」は直りません。実際に使う形で登録します。

これは辞書運用の考え方を変えました。「語を登録する」のではなく「原稿に出てくる文字列を登録する」と考えたほうが正確です。

落とし穴: 記号混じりのLatin語は辞書で直らない

これは辞書では原理的に無理でした。

J-SOX → 辞書に登録しても効かない
(トークナイザが J / - / SOX に分割するので、語として一致しない)

対策は、合成前のテキスト置換です。辞書の手前に、単純な文字列置換のテーブルを置きました。

J-SOX          → ジェイソックス
Privacy J-SOX  → プライバシージェイソックス
vERA           → ブイエラ
SaaS           → サース

つまり第1層は実質2段構えです。

replacements(事前テキスト置換)
  ↓
words(ユーザー辞書)
  ↓
合成

replacements が要るのは、辞書がトークナイザの語境界に依存するからです。語として認識されないものは、辞書には入りません。

語の前後に「間」を作る方法

新しい固有名詞は、前後に少し間があると聞き取りやすくなります。これの実装で1つ罠がありました。

エンジンには句のポーズを指定する機能があります。それでは駄目でした。

「統計作成等特例」に続けて助詞「と」がある場合、
エンジンは助詞を語句に吸着させる
  → 「統計作成等特例と」を1つの語句として扱う
  → 辞書の語一致が崩れる

正解はこうでした。

1. 語の前後でテキストを割って、別々に合成する
2. prePhonemeLength / postPhonemeLength = 0 にして、
   エンジンが自動で入れる余白を殺す
3. 自前で無音 0.3 秒を挟む

自動で入る余白を残したまま自前の無音を足すと、間が長すぎます。まず自動を切って、それから自分で入れるという順番です。

4. 第2層: アクセント推定

カナが正しくても、高低が違うと不自然に聞こえます。

ここは機械学習によるアクセント推定を入れました。Apache-2.0のライブラリを常駐サービスとして立て、合成側からHTTPで叩く構成です。モデルのロードが重いので、プロセスを都度起動する形にはできませんでした。

サービスが上がらなければ自動で無効化するようにしています。アクセント推定はあくまで品質向上で、これが原因でビルド全体が止まると本末転倒だからです。

アクセントはカナからは見えない

これが第2層の一番重要な性質です。

読みの確認をカナの一覧で行っていると、アクセントの誤りは1件も見つかりません。表記が同じで高低だけ違うからです。

なので工程にこれを入れました。

描画を抜いて全スライドの音声だけを生成し、耳で確認する。

動画の描画は1本で1時間以上かかります。音声だけなら数分です。読み合わせのループを高速化するために、音声だけ生成するモードを作りました。

実際に耳で確定した語があります。ある固有名詞のアクセント核の位置と、その語の前後の無音の長さは、聞いて決めました。カナの点検では絶対に出てきません。

5. 第3層: 文脈依存の文単位オーバーライド

辞書で潰せない最後の種類が、同形異音語です。

方   → かた(人)/ ほう(方向)
行った → おこなった / いった

辞書に 方→かた と登録すると、「方向のほう」まで壊れます。語単位では解けません。

そこで、文単位で「この文はこう読む」という修正を持つ層を作りました。キーが文、値がその文の誤読部分だけをカナにしたものです。

words(語単位)        ← 語として一意に決まるもの
reading_overrides(文単位) ← 文脈で変わるもの

この層に入れる修正をどう見つけるかが、実は一番難しい問題でした。全文を耳で聞くのは現実的ではないので、LLMに判定させることにしました。

そして、それが規模で壊れました。 79枚に広げたら、検証を通過した23件のうち18件が誤修正でした。その話は分量があるので別記事にしています(「LLM-as-judgeを79枚に広げたら、通過23件のうち18件が誤修正だった」)。

この記事の文脈で書いておくべき結論は1つです。

第3層は「自動で埋まる層」ではない。
機械の提案を、決定論のガードで削ってから入れる層である。

6. 人手の正解表を丸ごと辞書に入れてはいけない

案件によっては、人が作った正解読み表があります。講義資料の固有名詞リストのようなものです。

これを見つけたとき、素朴に「全部辞書に入れればいい」と考えました。間違いでした。

実測すると、96語の正解表のうち、エンジンの読みとズレていたのは6語だけでした。残り90語は元から正しく読めていました。

全部登録すると何が起きるか。

正解表の 96 語をすべて辞書に登録
  → 元から正しかった 90 語にも
    辞書のアクセント指定(平板など)が適用される
  → 正しかった読みが壊れる

辞書登録は「読みを与える」だけでなく「アクセントを固定する」操作です。指定しなければ既定値が入り、それがエンジンの推定より悪いことがあります。

なので工程を1段足しました。

正解表 と エンジンの実読み を突き合わせ、
ズレた語だけを辞書に入れる。

出力は「一致」「辞書で解決済み」「ズレ」の3分類にしました。何を触らなかったかも記録しておかないと、次に同じ判断ができません。

「正解データがあるなら全部入れる」は、既存の挙動が正しいかを確認していません。 差分だけ触る、という原則がここでも効きます。

7. まとめると層の順番が防御になっている

3層を並べると、こうなります。

層 何を潰すか 潰せないもの
0. 事前テキスト置換 記号混じりのLatin語 語として認識されるもの全部
1. 語単位辞書 固有名詞、熟語の訓読み誤り、複合語 文脈で変わるもの、高低
2. アクセント推定 高低 カナそのものの誤り
3. 文単位オーバーライド 文脈依存の同形異音語 (機械の提案を人がガードする)

そして順番に意味があります。

上の層で人が確定したものは、下の層の機械的な提案から保護されます。 第1層の辞書に人名を登録しておけば、第3層のLLMがその人名を蒸し返しても、辞書と重なる候補として自動的に落とせます。

逆に、第3層から先に手を付けると、人が確定した結論が機械の判定にさらされます。

まとめ

  1. チューニングの前にエンジンのバージョンを確認する。ここが一番効くことがある
  2. 長文一括ではなく文単位で合成する。文末にポーズを入れる
  3. 日本語の文分割は、閉じ括弧の直前で割らない
  4. 単独で正しく読める語が、複合語では別の読みで再発する。 実際に使う文字列で登録する
  5. 記号混じりのLatin語は辞書で直らない。語として認識されないものは辞書に入らない
  6. 語の前後の「間」は、句のポーズ指定では作れない。テキストを割って自前の無音を挟む
  7. 自動で入る余白を殺してから、自分で余白を入れる
  8. アクセント(高低)はカナからは見えない。 音声を生成して耳で確認する工程が必要
  9. そのために、描画を抜いて音声だけ生成する高速ループを用意する
  10. 文脈依存の同形異音語は語単位では解けない。文単位の層が要る
  11. 人手の正解表を丸ごと辞書に入れてはいけない。 ズレた語だけを入れる
  12. 辞書登録は読みを与えるだけでなく、アクセントを固定する操作でもある
  13. 層の順番が防御になる。上の層で人が確定したものが、下の層の機械的提案から守られる

日本語の読み上げ品質は、「辞書に登録する」という単一の作業では作れませんでした。何が語として認識されるか、何が文脈に依存するか、何がカナに現れないか。性質ごとに層を分けて、それぞれ違う方法で確認するという形になりました。

そしてこの構造は、前に書いたインフラ側の記事と同じ形だと思っています。単一の強い関門ではなく、見落としの方向が違う関門を重ねる。品質の作り方としては、領域が変わっても同じ形に落ち着くようです。


この記事は 七夕研究所 Qiita Organization の記事です。開発と運用で実際に踏んだものを順次書いています。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?