Closed10

「MeloTTS」で日本語音声の学習を試す

kun432kun432

学習データは以下の「ずんだもん」の音声データを使用させていただく。

https://zunko.jp/multimodal_dev/login.php

上記ページにアクセス、規約に同意の上、ログイン&音声データをダウンロードする。

データは2種類のコーパスにあわせてそれぞれ用意されている。

ROHANとは

コーパス文の課題生成システムが提示した条件を満たす文章を人間が作成することで,常用漢字と読みを全て含み,出現頻度の低いモーラもカバーした4600文章からなる日本語のテキストコーパスです.4600文は,後述する22のサブセットから構成されており,サブセット単位で全モーラを最低2回含みます.なお,ここでの全モーラとは,Sinsyの日本語でサポートするモーラと定義します.

ITAコーパスとは

著作権の消滅した文献やオリジナルの文章・単語から文セットを構築することで,パブリックドメインで公開される文章コーパスです.日本語の単語では出現しにくいモーラも一定量カバーしつつも読みやすさを考慮しています.424文は,100文 (Emotion)と324文 (Recitation)のサブセットで構成されており,用途に応じて使い分けることが可能です.分野横断的研究を加速させるコーパスをという思いを込めて,Inter-field Task Accelerating (ITA)コーパスと命名しました.

使用したいボイスデータをダウンロードする。今回は、「ITAコーパスマルチモーダルデータベース ずんだもん ノーマル 朗読324」(約400MB)を使用して学習を行っていくこととする。環境は以下。

  • Ubnuntu-22.04
  • RTX4090(VRAM24GB)
  • Python 3.11.5
  • CUDA-12.6

学習環境の構築

レポジトリをクローン

git clone https://github.com/myshell-ai/MeloTTS && cd MeloTTS

Python仮想環境を作成

python -m venv .venv
. .venv/bin/activate

作業はJupyterLab上で行うので、JupyterLabをインストール

pip install -U pip jupyterlab ipywidgets

JupyterLabを起動

jupyter-lab --ip="0.0.0.0" --NotebookApp.token=""

以後の作業はJupyterLab上で行う。

まずパッケージをインストール

pip install -e .

辞書もダウンロードしておく

!python -m unidic download

とりあえずこれでMeloTTSが動作する状態なった。トレーニング途中でTTSを試したりすることもあるので、一旦動かしておく。

from melo.api import TTS

speed = 1.0
device = 'auto'

text = "おはようございます。今日はいいお天気ですね。競馬観戦にはもってこいですね。"
model = TTS(language='JP', device=device)
speaker_ids = model.hps.data.spk2id

output_path = 'sample.wav'
model.tts_to_file(text, speaker_ids['JP'], output_path, speed=speed)
出力
 > Text split to sentences.
おはようございます. 今日はいいお天気ですね.
競馬観戦にはもってこいですね.
 > ===========================

再生してみる。

from IPython.display import Audio, display

display(Audio("sample.wav", autoplay=True))

音声が再生されればOK。

学習の準備

では学習に向けて準備を行う。

まず、トレーニング用のコードがあるディレクトリに移動。ファイルブラウザの方も該当のディレクトリに移動しておく。

%cd melo

ダウンロードしておいた「ITAコーパスマルチモーダルデータベース ずんだもん ノーマル 朗読324」をアップロードする。ファイル名はITA_recitation_nomal_synchronized_wav.zipというファイル名になっていると思う。

ファイルアップロードが終わったら解凍。

!unzip -q ITA_recitation_nomal_synchronized_wav.zip

中身を見てみる。

!tree ITA_recitation_nomal_synchronized_wav | head
出力
ITA_recitation_nomal_synchronized_wav
├── recitation001.wav
├── recitation002.wav
├── recitation003.wav
├── recitation004.wav
├── recitation005.wav
├── recitation006.wav
├── recitation007.wav
├── recitation008.wav
├── recitation009.wav

こんな感じでWAVファイルが324個(実際にはあるファイルのコピーが1つ紛れているので325個)作成される。

次にITAコーパスの朗読用文章リストをダウンロードする。

!wget https://raw.githubusercontent.com/mmorise/ita-corpus/refs/heads/main/recitation_transcript_utf8.txt

中身は以下のようになっている。

!head recitation_transcript_utf8.txt
出力
RECITATION324_001:女の子がキッキッ嬉しそう。,オンナノコガキッキッウレシソー。
RECITATION324_002:ツァツォに旅行した。,ツァツォニリョコーシタ。
RECITATION324_003:民衆がテュルリー宮殿に侵入した。,ミンシュウガテュルリーキュウデンニシンニュウシタ。
RECITATION324_004:ハイチ共和国でトゥーサンルーヴェルテュールが勝利を収められたのは、実際黄熱病のおかげだった。,ハイチキョーワコクデトゥーサンルーヴェルテュールガショーリヲオサメラレタノワ、ジッサイオーネツビョーノオカゲダッタ。
RECITATION324_005:レジャンドルは民衆をテュルリー宮殿に招いた。,レジャンドルワミンシュウヲテュルリーキュウデンニマネータ。
RECITATION324_006:助言はできないとデュパンは言った。,ジョゲンワデキナイトデュパンワイッタ。
RECITATION324_007:フランス人シェフと日本人シェフは全然違う。,フランスジンシェフトニホンジンシェフワゼンゼンチガウ。
RECITATION324_008:中国の外交団にアタッシェとして派遣された。,チュウゴクノガイコーダンニアタッシェトシテハケンサレタ。
RECITATION324_009:ファシズム勢力との総力戦に臨む。,ファシズムセーリョクトノソーリョクセンニノゾム。
RECITATION324_010:家具商人のフィシェルは、荷車と仔馬を貸してくれた。,カグショーニンノフィシェルワ、ニグルマトコウマヲカシテクレタ。

RECITATION324_***の数字部分が各WAVファイルrecitation***.wavの数字部分と対応しており、記載されている内容がWAVファイルで発話されている音声のテキストになる。

これを使って学習用メタデータを作成するのだが、その前に適当に音声ファイルを1つ選んで波形を見てみる。以下はAudacityの例。

これを見ると、

  • 前後に無音区間がある。
  • -50dBあたりが無音のしきい値に見える。

のがわかる。

自分はこういった音声データの前処理に詳しくないのだが、発話テキストにはこういった無音に関する情報がないことを踏まえると、無音区間は学習上問題になりそう、特に発話前の部分は影響しそうに思う。逆に、発話後の無音区間については、長すぎるのは問題になるとは思うが、通常何かしらの句読点のあとには一定の無音があるのは普通(文と文の区切り)だと思う。これらを踏まえて以下のような前処理を行おうと思う。

  • -50dBをしきい値にして無音部分をカットする
  • 発話前の無音はほぼカット、発話後の無音は一定時間残す。

それ以外にもいろいろな前処理があると思うが(ノイズカットとか音量とか)、今回はデータセットとして提供されているものなので、そのあたりは問題ないものとして進めようと思う。

あと、学習向けのドキュメントにもあるが、MeloTTSの推奨するWAVファイルのサンプリングレートは44100Hzになっているのに対し、今回の音声データは96000Hzになっている。

!ffprobe ITA_recitation_nomal_synchronized_wav/recitation001.wav
出力
Input #0, wav, from 'ITA_recitation_nomal_synchronized_wav/recitation001.wav':
  Duration: 00:00:05.70, bitrate: 2304 kb/s
  Stream #0:0: Audio: pcm_s24le ([1][0][0][0] / 0x0001), 96000 Hz, 1 channels, s32 (24 bit), 2304 kb/s

よって、この変換もあわせて行う。どちらもpydubでできる。

pip install pydub tqdm
import os
from pydub import AudioSegment
from pydub.silence import detect_nonsilent
from tqdm import tqdm

def trim_silence_and_resample(
        input_file,
        output_file,
        silence_threshold=-50.0,
        chunk_size=10,
        start_silence_ms=100,
        end_silence_ms=700,
        target_sample_rate=44100
    ):
    # 音声ファイルを読み込み
    audio = AudioSegment.from_file(input_file, format="wav")
    
    # 無音ではない部分を検出
    non_silent_ranges = detect_nonsilent(audio, min_silence_len=chunk_size, silence_thresh=silence_threshold)
    if not non_silent_ranges:  # 無音でない部分が見つからなかった場合、スキップ
        return False

    # 無音でない最初の部分と最後の部分を取得
    start_trim = non_silent_ranges[0][0]
    end_trim = non_silent_ranges[-1][1]

    # 先頭と末尾に一定の無音を考慮
    start_trim_w_silence = max(0, start_trim - start_silence_ms)
    end_trim_w_silence = min(len(audio), end_trim + end_silence_ms) 
    
    # トリミング
    audio = audio[start_trim_w_silence:end_trim_w_silence]
    
    # サンプリングレートを44100に変更
    audio = audio.set_frame_rate(target_sample_rate)
    
    # トリミングしてリサンプルした音声を保存
    audio.export(output_file, format="wav")
    return True

def preprocess_audio_dir(
        input_dir,
        output_dir,
        silence_threshold=-50.0,
        chunk_size=10,
        start_silence_ms=100,
        end_silence_ms=700,
        target_sample_rate=44100
    ):
    os.makedirs(output_dir, exist_ok=True)
    
    files = [f for f in os.listdir(input_dir) if f.lower().endswith(".wav")]
    for file_name in tqdm(files, desc="Processing files"):
        input_file = os.path.join(input_dir, file_name)
        output_file = os.path.join(output_dir, file_name)
        
        # 無音カット&サンプリングレート変更
        success = trim_silence_and_resample(input_file, output_file, silence_threshold, chunk_size, tail_length_ms, target_sample_rate)
        if not success:
            tqdm.write(f"Skipped {file_name} (no non-silent part detected)")

今回は文末に700ms、先頭部分に100msの無音区間を考慮してトリムしてみた。日本語における文と文の間の無音区間=ポーズ時間についてはいろいろ研究があるようで、色々見て回った感じ、普通の会話スピードなら700msぐらいが平均のように思えたので、それを設定した。先頭部分はバツっと切ってしまうのもどうなのかな?と思ったので、少しだけ付与した感じ。なお、これらはあくまでも自分の私見で設定してるだけなので、これが正しいのかはわからない。まあ学習した結果で判断することとする。

ディレクトリに対してまるっと処理する。

preprocess_audio_dir("ITA_recitation_nomal_synchronized_wav", "ita_wav_out")
出力
Processing files: 100%|█████████████████████████████| 325/325 [00:24<00:00, 13.38it/s]

これで音声データの前処理は完了。前処理前後でどのように変更されているかは実際に確認してみると良いと思う。

次にメタデータファイルを作成する。メタデータファイルは、音声ファイル・スピーカー名・言語・発話テキストをリストにまとめたもの。ドキュメントでは以下のようなフォーマットと記載がある。

path/to/audio_001.wav |<speaker_name>|<language_code>|<text_001>
path/to/audio_002.wav |<speaker_name>|<language_code>|<text_002>

この記載少しだけ不備があって、音声ファイルのパスの末尾にスペースがあるように書かれているのだが、実際にはこれは不要。実際のメタデータファイルのサンプルが用意されているので、こちらを見ると良い。

https://github.com/myshell-ai/MeloTTS/blob/main/melo/data/example/metadata.list

ではメタデータファイルを作成する。ITAコーパスの朗読用文章リストを使う。

speaker_name = "Zundamon"
language_code = "JP"
content_dir = "ita_wav_out"

data = []
with open("recitation_transcript_utf8.txt", "r", encoding="utf-8") as f:
    for line in f.readlines():
        line = line.strip()
        _id, _sentences = line.split(":")
        _, id = _id.split("_")
        sentence, _ = _sentences.split(",")
        file_name = f"recitation{id}.wav"
        data.append(f"{content_dir}/{file_name}|{speaker_name}|{language_code}|{sentence}")

with open("metadata.list", "w", encoding="utf-8") as f:
    f.write("\n".join(data))

これでメタデータファイルが以下のような内容で作成される。

!head metadata.list
出力
ita_wav_out/recitation001.wav|Zundamon|JP|女の子がキッキッ嬉しそう。
ita_wav_out/recitation002.wav|Zundamon|JP|ツァツォに旅行した。
ita_wav_out/recitation003.wav|Zundamon|JP|民衆がテュルリー宮殿に侵入した。
ita_wav_out/recitation004.wav|Zundamon|JP|ハイチ共和国でトゥーサンルーヴェルテュールが勝利を収められたのは、実際黄熱病のおかげだった。
ita_wav_out/recitation005.wav|Zundamon|JP|レジャンドルは民衆をテュルリー宮殿に招いた。
ita_wav_out/recitation006.wav|Zundamon|JP|助言はできないとデュパンは言った。
ita_wav_out/recitation007.wav|Zundamon|JP|フランス人シェフと日本人シェフは全然違う。
ita_wav_out/recitation008.wav|Zundamon|JP|中国の外交団にアタッシェとして派遣された。
ita_wav_out/recitation009.wav|Zundamon|JP|ファシズム勢力との総力戦に臨む。
ita_wav_out/recitation010.wav|Zundamon|JP|家具商人のフィシェルは、荷車と仔馬を貸してくれた。

これで学習に必要なデータの準備も完了したので学習に進む。

kun432kun432

学習

ではいよいよ学習。

まず、メタデータファイルを前処理するためのスクリプトを実行する。

python preprocess_text.py --metadata metadata.list 

warningがいろいろ出るのだけど一旦気にせず。

出力
100%|████████████████████████████████████████| 324/324 [00:02<00:00, 119.79it/s]

これにより以下の4ファイルが生成される

  • config.json: 学習時のハイパーパラメータ等を記載した設定ファイル。必要ならば個別に修正する(例えばCUDA out-of-memoryが起きたりする場合はバッチサイズを小さくするなど)。学習したモデルを使用して推論する際にも必要になる。
config.jsonの中身(長いので折りたたみ)
config.json
{
  "train": {
    "log_interval": 200,
    "eval_interval": 1000,
    "seed": 52,
    "epochs": 10000,
    "learning_rate": 0.0003,
    "betas": [
      0.8,
      0.99
    ],
    "eps": 1e-09,
    "batch_size": 6,
    "fp16_run": false,
    "lr_decay": 0.999875,
    "segment_size": 16384,
    "init_lr_ratio": 1,
    "warmup_epochs": 0,
    "c_mel": 45,
    "c_kl": 1.0,
    "skip_optimizer": true
  },
  "data": {
    "training_files": "train.list",
    "validation_files": "val.list",
    "max_wav_value": 32768.0,
    "sampling_rate": 44100,
    "filter_length": 2048,
    "hop_length": 512,
    "win_length": 2048,
    "n_mel_channels": 128,
    "mel_fmin": 0.0,
    "mel_fmax": null,
    "add_blank": true,
    "n_speakers": 1,
    "cleaned_text": true,
    "spk2id": {
      "Zundamon": 0
    }
  },
  "model": {
    "use_spk_conditioned_encoder": true,
    "use_noise_scaled_mas": true,
    "use_mel_posterior_encoder": false,
    "use_duration_discriminator": true,
    "inter_channels": 192,
    "hidden_channels": 192,
    "filter_channels": 768,
    "n_heads": 2,
    "n_layers": 6,
    "n_layers_trans_flow": 3,
    "kernel_size": 3,
    "p_dropout": 0.1,
    "resblock": "1",
    "resblock_kernel_sizes": [
      3,
      7,
      11
    ],
    "resblock_dilation_sizes": [
      [
        1,
        3,
        5
      ],
      [
        1,
        3,
        5
      ],
      [
        1,
        3,
        5
      ]
    ],
    "upsample_rates": [
      8,
      8,
      2,
      2,
      2
    ],
    "upsample_initial_channel": 512,
    "upsample_kernel_sizes": [
      16,
      16,
      8,
      2,
      2
    ],
    "n_layers_q": 3,
    "use_spectral_norm": false,
    "gin_channels": 256
  },
  "num_languages": 8,
  "num_tones": 16,
  "symbols": [
    "_",
    "\"",
    "(",
    ")",
    "*",
    "/",
    ":",
    "AA",
    "E",
    "EE",
    "En",
    "N",
    "OO",
    "Q",
    "V",
    "[",
    "\\",
    "]",
    "^",
    "a",
    "a:",
    "aa",
    "ae",
    "ah",
    "ai",
    "an",
    "ang",
    "ao",
    "aw",
    "ay",
    "b",
    "by",
    "c",
    "ch",
    "d",
    "dh",
    "dy",
    "e",
    "e:",
    "eh",
    "ei",
    "en",
    "eng",
    "er",
    "ey",
    "f",
    "g",
    "gy",
    "h",
    "hh",
    "hy",
    "i",
    "i0",
    "i:",
    "ia",
    "ian",
    "iang",
    "iao",
    "ie",
    "ih",
    "in",
    "ing",
    "iong",
    "ir",
    "iu",
    "iy",
    "j",
    "jh",
    "k",
    "ky",
    "l",
    "m",
    "my",
    "n",
    "ng",
    "ny",
    "o",
    "o:",
    "ong",
    "ou",
    "ow",
    "oy",
    "p",
    "py",
    "q",
    "r",
    "ry",
    "s",
    "sh",
    "t",
    "th",
    "ts",
    "ty",
    "u",
    "u:",
    "ua",
    "uai",
    "uan",
    "uang",
    "uh",
    "ui",
    "un",
    "uo",
    "uw",
    "v",
    "van",
    "ve",
    "vn",
    "w",
    "x",
    "y",
    "z",
    "zh",
    "zy",
    "~",
    "æ",
    "ç",
    "ð",
    "ø",
    "ŋ",
    "œ",
    "ɐ",
    "ɑ",
    "ɒ",
    "ɔ",
    "ɕ",
    "ə",
    "ɛ",
    "ɜ",
    "ɡ",
    "ɣ",
    "ɥ",
    "ɦ",
    "ɪ",
    "ɫ",
    "ɬ",
    "ɭ",
    "ɯ",
    "ɲ",
    "ɵ",
    "ɸ",
    "ɹ",
    "ɾ",
    "ʁ",
    "ʃ",
    "ʊ",
    "ʌ",
    "ʎ",
    "ʏ",
    "ʑ",
    "ʒ",
    "ʝ",
    "ʲ",
    "ˈ",
    "ˌ",
    "ː",
    "̃",
    "̩",
    "β",
    "θ",
    "ᄀ",
    "ᄁ",
    "ᄂ",
    "ᄃ",
    "ᄄ",
    "ᄅ",
    "ᄆ",
    "ᄇ",
    "ᄈ",
    "ᄉ",
    "ᄊ",
    "ᄋ",
    "ᄌ",
    "ᄍ",
    "ᄎ",
    "ᄏ",
    "ᄐ",
    "ᄑ",
    "ᄒ",
    "ᅡ",
    "ᅢ",
    "ᅣ",
    "ᅤ",
    "ᅥ",
    "ᅦ",
    "ᅧ",
    "ᅨ",
    "ᅩ",
    "ᅪ",
    "ᅫ",
    "ᅬ",
    "ᅭ",
    "ᅮ",
    "ᅯ",
    "ᅰ",
    "ᅱ",
    "ᅲ",
    "ᅳ",
    "ᅴ",
    "ᅵ",
    "ᆨ",
    "ᆫ",
    "ᆮ",
    "ᆯ",
    "ᆷ",
    "ᆸ",
    "ᆼ",
    "ㄸ",
    "!",
    "?",
    "…",
    ",",
    ".",
    "'",
    "-",
    "¿",
    "¡",
    "SP",
    "UNK"
  ]
}
  • metadata.list.cleaned: メタデータファイルを前処理したファイル。メタデータファイルの内容を下に以下のような情報が付与されている模様。
    • 発話テキストを正規化したテキスト
    • テキストを音素(phoneme)に変換したテキスト
    • 各音素に対応する声調またはアクセント情報
    • 各単語が何個の音素に分解されるかを示すリスト
metadata.list.cleaned
ita_wav_out/recitation001.wav|Zundamon|JP|オンナノコガキッキッウレシソウ|_ o N n a n o k o g a k i q k i q u r e sh i s o u _|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|1 3 3 2 2 2 2 2 2 2 2 2 1
ita_wav_out/recitation002.wav|Zundamon|JP|ツァツォニリョコウシタ|_ ts a ts o n i ry o k o u sh i t a _|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|1 3 2 2 2 2 2 2 1
ita_wav_out/recitation003.wav|Zundamon|JP|ミンシュウガテュルリーキュウデンニシンニュウシタ|_ m i N sh u u g a ty u r u r i ky u u d e N n i sh i N ny u u sh i t a _|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|1 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 1
ita_wav_out/recitation004.wav|Zundamon|JP|ハイチキョウワコクデトゥーサンルーヴェルテュールガショウリヲオサメラレタノハジッサイオウネツビョウノオカゲダッタ|_ h a i ch i ky o u w a k o k u d e t u s a N r u b e r u t e y u r u g a sh o u r i o o s a m e r a r e t a n o h a j i q s a i o u n e ts u by o u n o o k a g e d a q t a _|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|1 5 1 1 2 1 4 2 2 3 2 3 3 1 1 2 2 3 2 1 2 1 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1
ita_wav_out/recitation005.wav|Zundamon|JP|レジャンドルハミンシュウヲテュルリーキュウデンニマネイタ|_ r e j a N d o r u h a m i N sh u u o ty u r u r i ky u u d e N n i m a n e i t a _|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|1 3 2 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1
ita_wav_out/recitation006.wav|Zundamon|JP|ジョゲンハデキナイトデュパンハイッタ|_ j o g e N h a d e k i n a i t o dy u p a N h a i q t a _|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|1 3 3 3 3 3 3 3 2 2 2 1
ita_wav_out/recitation007.wav|Zundamon|JP|フランスニンシェフトニホンジンシェフハゼンゼンチガウ|_ f u r a N s u n i N sh e f u t o n i h o N j i N sh e f u h a z e N z e N ch i g a u _|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|1 7 3 3 3 3 3 3 2 2 2 2 2 2 2 2 1
ita_wav_out/recitation008.wav|Zundamon|JP|チュウゴクノガイコウダンニアタッシェトシテハケンサレタ|_ ch u u g o k u n o g a i k o u d a N n i a t a q sh e t o sh i t e h a k e N s a r e t a _|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|1 2 1 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1
ita_wav_out/recitation009.wav|Zundamon|JP|ファシズムセイリョクトノソウリョクセンニノゾム|_ f a sh i z u m u s e i ry o k u t o n o s o u ry o k u s e N n i n o z o m u _|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|1 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 1
ita_wav_out/recitation010.wav|Zundamon|JP|カグショウニンノフィシェルハニグルマトコウマヲカシテクレタ|_ k a g u sh o u n i N n o f i sh e r u h a n i g u r u m a t o k o u m a o k a sh i t e k u r e t a _|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|1 2 2 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 1
(snip)
  • train.list: トレーニング用ファイル。metadata.list.cleanedから抽出される。
train.list
ita_wav_out/recitation231.wav|Zundamon|JP|セツボウシツツシュヲマツ|_ s e ts u b o u sh i ts u ts u sh u o m a ts u _|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|1 3 3 2 2 2 2 2 2 2 1
ita_wav_out/recitation286.wav|Zundamon|JP|テョサンハズィーブラヲミタ|_ ty o s a N h a z i b u r a o m i t a _|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|1 2 2 2 2 2 2 2 2 1 1 1
ita_wav_out/recitation075.wav|Zundamon|JP|ユビヲクワエテピュュトヒトコエクチブエヲフイタ|_ y u b i o k u w a e t e py u y u t o h i t o k o e k u ch i b u e o f u i t a _|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1
ita_wav_out/recitation229.wav|Zundamon|JP|プロヤキュウハドノチームガユウショウスルダロウ|_ p u r o y a ky u u h a d o n o ch i m u g a y u u sh o u s u r u d a r o u _|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|1 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1
ita_wav_out/recitation074.wav|Zundamon|JP|キレアジスルドイペティナイフハツカイガッテガヨイ|_ k i r e a j i s u r u d o i p e t i n a i f u h a ts u k a i g a q t e g a y o i _|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|1 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1
ita_wav_out/recitation165.wav|Zundamon|JP|ヒメヤジジョタチガキンポウゲヤタンポポノハナヲモッテカノホウヘカケヨッテイッタ|_ h i m e y a j i j o t a ch i g a k i N p o u g e y a t a N p o p o n o h a n a o m o q t e k a n o h o u h e k a k e y o q t e i q t a _|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|1 4 2 2 2 2 2 2 2 2 2 2 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1
ita_wav_out/recitation192.wav|Zundamon|JP|フジンガギョウテンシタノモムリハナイ|_ f u j i N g a gy o u t e N sh i t a n o m o m u r i h a n a i _|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|1 3 3 3 3 3 3 2 2 2 2 2 2 1
ita_wav_out/recitation096.wav|Zundamon|JP|テンジカイデアノサクヒンノミフヒョウダッタ|_ t e N j i k a i d e a n o s a k u h i N n o m i f u hy o u d a q t a _|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|1 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 1
ita_wav_out/recitation063.wav|Zundamon|JP|ナツヤスミニトラアヴェミュンヘリョコウシタ|_ n a ts u y a s u m i n i t o r a a b e my u N h e ry o k o u sh i t a _|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|1 3 3 3 3 3 2 2 2 2 2 2 2 2 2 1
ita_wav_out/recitation304.wav|Zundamon|JP|ティファニーハパピーニムギュットダキツキナガラチュチュットキスヲシセンキュトイッタ|_ t i f a n i h a p a p i n i m u gy u q t o d a k i ts u k i n a g a r a ch u ch u q t o k i s u o sh i s e N ky u t o i q t a _|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|1 2 2 2 2 2 2 2 2 2 1 2 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 1
(snip)
  • val.list: 評価用ファイル。metadata.list.cleanedから抽出される。
val.list
ita_wav_out/recitation091.wav|Zundamon|JP|ホンバンマエハメチャメチャフアンニナル|_ h o N b a N m a e h a m e ch a m e ch a f u a N n i n a r u _|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|1 3 3 3 2 2 2 2 2 2 2 2 2 2 1
ita_wav_out/recitation100.wav|Zundamon|JP|チサイホシヲタクサンエガイタミズノミミグラスハヨクアル|_ ch i s a i h o sh i o t a k u s a N e g a i t a m i z u n o m i m i g u r a s u h a y o k u a r u _|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|1 2 2 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1
ita_wav_out/recitation049.wav|Zundamon|JP|シャチョウカラノシジデス|_ sh a ch o u k a r a n o sh i j i d e s u _|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|1 3 3 3 3 3 2 2 1
ita_wav_out/recitation105.wav|Zundamon|JP|コノホウセキハヒトツヒャクマンエンイジョウノオネダンデス|_ k o n o h o u s e k i h a h i t o ts u hy a k u m a N e N i j o u n o o n e d a N d e s u _|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1

また、上記以外に、WAVファイルも処理されて、BERT特徴量ファイル(*.bert.pt)が生成されている様子。

!ls -lt ita_wav_out/recitation00*
出力
-rw-rw-r-- 1 kun432 kun432  164051 12月 29 07:38 ita_wav_out/recitation001.bert.pt
-rw-rw-r-- 1 kun432 kun432  512664 12月 29 05:40 ita_wav_out/recitation001.wav
-rw-rw-r-- 1 kun432 kun432  108755 12月 29 07:38 ita_wav_out/recitation002.bert.pt
-rw-rw-r-- 1 kun432 kun432  372248 12月 29 05:40 ita_wav_out/recitation002.wav
-rw-rw-r-- 1 kun432 kun432  213203 12月 29 07:38 ita_wav_out/recitation003.bert.pt
-rw-rw-r-- 1 kun432 kun432  628028 12月 29 05:40 ita_wav_out/recitation003.wav
-rw-rw-r-- 1 kun432 kun432  526547 12月 29 07:38 ita_wav_out/recitation004.bert.pt
-rw-rw-r-- 1 kun432 kun432 1430472 12月 29 05:40 ita_wav_out/recitation004.wav
-rw-rw-r-- 1 kun432 kun432  256211 12月 29 07:38 ita_wav_out/recitation005.bert.pt
-rw-rw-r-- 1 kun432 kun432  743748 12月 29 05:40 ita_wav_out/recitation005.wav
-rw-rw-r-- 1 kun432 kun432  182483 12月 29 07:38 ita_wav_out/recitation006.bert.pt
-rw-rw-r-- 1 kun432 kun432  547412 12月 29 05:40 ita_wav_out/recitation006.wav
-rw-rw-r-- 1 kun432 kun432  268499 12月 29 07:38 ita_wav_out/recitation007.bert.pt
-rw-rw-r-- 1 kun432 kun432  735984 12月 29 05:40 ita_wav_out/recitation007.wav
-rw-rw-r-- 1 kun432 kun432  280787 12月 29 07:38 ita_wav_out/recitation008.bert.pt
-rw-rw-r-- 1 kun432 kun432  755388 12月 29 05:40 ita_wav_out/recitation008.wav
-rw-rw-r-- 1 kun432 kun432  243923 12月 29 07:38 ita_wav_out/recitation009.bert.pt
-rw-rw-r-- 1 kun432 kun432  626264 12月 29 05:40 ita_wav_out/recitation009.wav

で、学習スクリプトを実行する・・・

bash train.sh config.json 1

のが本来の流れなのだけど、ちょっと気になったことがある。

上のmetadata.list.cleanedの正規化済みテキストを見ていると、元の文章にあった句読点が全く存在していない。抜粋すると以下。

metadata.listの一部
ita_wav_out/recitation023.wav|Zundamon|JP|フィレンツェ、パドヴァ、ヴェネツィアはどれもイタリアの都市です。
metadata.list.cleanedの一部
ita_wav_out/recitation023.wav|Zundamon|JP|フィレンツェパドヴァヴェネツィアハドレモイタリアノトシデス|_ f i r e N ts e p a d o b a b e n e ts i a h a d o r e m o i t a r i a n o t o sh i d e s u _|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|1 7 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 1

実際にこのまま学習を進めてみたところ、学習したモデルで生成された音声は、文中や文末にある句読点によるポーズがまったく存在しないような内容で生成されてしまっているように感じた。具体的にいうと、

おはようございます。
今日は良いお天気ですね。
競馬観戦には持ってこいですね。

でTTSすると

おはようございます今日は良いお天気ですね競馬観戦には持ってこいですね

みたいな感じ。

自分は10数時間程度学習させたもので試したのだけども、もっと学習時間を増やしていけば、もしかすると改善するのかもしれない。ただ、起きた事象からすると関係してそうな気がするので、少し深堀りしてみる。

kun432kun432

元々サンプルで用意されているメタデータファイルがある。

!head -5 data/example/metadata.list
出力
data/example/wavs/000.wav|EN-default|EN|Well, there are always new trends and styles emerging in the fashion world, but I think some of the biggest trends at the moment include sustainability and ethical fashion, streetwear and athleisure, and oversized and deconstructed silhouettes.
data/example/wavs/001.wav|EN-default|EN|Many designers and brands are focusing on creating more environmentally-friendly and socially responsible clothing, while others are incorporating elements of sportswear and casual wear into their collections.
data/example/wavs/002.wav|EN-default|EN|And there's a growing interest in looser, more relaxed shapes and unconventional materials and finishes.
data/example/wavs/003.wav|EN-default|EN|That's really insightful.
data/example/wavs/004.wav|EN-default|EN|What do you think are some of the benefits of following fashion trends?

これは英語のものになっているが、これを使って前処理を実行してみた。

!python preprocess_text.py --metadata data/example/metadata.list

サンプルのメタデータファイルと同じパスに前処理後のファイルが生成される。

!ls -lt data/example/
出力
合計 40
-rw-rw-r-- 1 kun432 kun432  4018 12月 29 08:37 config.json
-rw-rw-r-- 1 kun432 kun432 10442 12月 29 08:37 metadata.list.cleaned
-rw-rw-r-- 1 kun432 kun432  8620 12月 29 08:37 train.list
-rw-rw-r-- 1 kun432 kun432  1822 12月 29 08:37 val.list
drwxrwxr-x 2 kun432 kun432  4096 12月 29 08:37 wavs
-rw-rw-r-- 1 kun432 kun432  2860 12月 29 02:12 metadata.list

metadata.list.cleanedを見てみる

!head -5 data/example/metadata.list.cleaned
出力
data/example/wavs/000.wav|EN-default|EN|well, there are always new trends and styles emerging in the fashion world, but i think some of the biggest trends at the moment include sustainability and ethical fashion, streetwear and athleisure, and oversized and deconstructed silhouettes.|_ w eh l , dh eh r aa r ao l w ey z n uw t r eh n d z ae n d s t ay l z ih m er jh ih ng ih n dh ah f ae sh ah n w er l d , b ah t ay th ih ng k s ah m ah V dh ah b ih g ah s t t r eh n d z ae t dh ah m ow m ah n t ih n k l uw d s ah s t ey n ah b ih l ih t iy ae n d eh th ah k ah l f ae sh ah n , s t r iy t w eh r ae n d ae th ah l hh y uw r , ae n d ow V er s ay z d ae n d d iy k ah n s t r ah k t ah d s ih l ah w eh t s . _|0 0 2 0 0 0 2 0 2 0 2 0 0 3 0 0 2 0 0 2 0 0 0 2 0 0 0 0 2 0 0 1 0 2 0 1 0 1 0 0 1 0 2 0 1 0 0 2 0 0 0 0 2 0 2 0 2 0 0 0 2 0 2 0 0 1 0 2 0 1 0 0 0 0 2 0 0 0 2 0 0 1 0 2 0 1 0 0 1 0 0 0 2 0 0 1 0 0 3 0 1 0 2 0 1 0 1 2 0 0 2 0 1 0 1 0 0 2 0 1 0 0 0 0 0 2 0 0 3 0 2 0 0 2 0 1 0 0 0 3 0 0 2 0 0 2 0 1 0 2 0 0 2 0 0 0 3 0 1 0 0 0 0 2 0 0 1 0 0 3 0 1 0 2 0 0 0 0|1 3 1 3 2 5 2 6 3 5 6 2 2 5 4 1 3 1 4 3 2 2 6 6 2 2 6 6 13 3 6 5 1 4 4 3 2 2 2 2 1 3 7 3 4 3 3 3 4 4 1 1
data/example/wavs/001.wav|EN-default|EN|many designers and brands are focusing on creating more environmentally-friendly and socially responsible clothing, while others are incorporating elements of sportswear and casual wear into their collections.|_ m eh n iy d ih z ay n er z ae n d b r ae n d z aa r f ow k ah s ih ng aa n k r iy ey t ih ng m ao r ih n V ay r ah n m eh n t ah l iy - f r eh n d l iy ae n d s ow sh ah l iy r iy s p aa n s ah b ah l k l ow dh ih ng , w ay l ah dh er z aa r ih n k ao r p er ey t ih ng eh l ah m ah n t s ah V s p ao r t s w eh r ae n d k ae zh ah w ah l w eh r ih n t uw dh eh r k ah l eh k sh ah n z . _|0 0 2 0 1 0 1 0 2 0 1 0 2 0 0 0 0 2 0 0 0 2 0 0 2 0 1 0 1 0 2 0 0 0 1 2 0 1 0 0 2 0 1 0 0 3 0 1 0 0 2 0 0 1 0 1 0 0 0 2 0 0 0 1 2 0 0 0 2 0 1 0 1 0 1 0 0 2 0 0 1 0 1 0 0 0 2 0 1 0 0 0 2 0 2 0 1 0 2 0 1 0 0 2 0 0 1 3 0 1 0 2 0 1 0 1 0 0 0 2 0 0 0 2 0 0 0 0 3 0 2 0 0 0 2 0 1 0 1 0 0 2 0 1 0 0 2 0 2 0 0 1 0 2 0 0 1 0 0 0 0|1 4 7 3 6 2 7 2 7 3 14 1 7 3 6 11 6 1 3 4 2 11 8 2 5 4 3 7 3 4 3 9 1 1
data/example/wavs/002.wav|EN-default|EN|and there's a growing interest in looser, more relaxed shapes and unconventional materials and finishes.|_ ae n d dh eh r ' eh s ah g r ow ih ng ih n t r ah s t ih n l uw s er , m ao r r ih l ae k s t sh ey p s ae n d ah n k ah n V eh n sh ah n ah l m ah t ih r iy ah l z ae n d f ih n ih sh ih z . _|0 2 0 0 0 2 0 0 2 0 1 0 0 2 1 0 2 0 0 0 1 0 0 1 0 0 2 0 1 0 0 2 0 0 1 0 2 0 0 0 0 2 0 0 2 0 0 3 0 0 1 0 0 2 0 0 1 0 1 0 0 1 0 2 0 1 1 0 0 2 0 0 0 2 0 1 0 1 0 0 0|1 3 3 1 2 1 5 7 2 2 2 1 3 7 4 3 13 9 3 7 1 1
data/example/wavs/003.wav|EN-default|EN|that's really insightful.|_ dh ae t ' eh s r ih l iy ih n s ay t f ah l . _|0 0 2 0 0 2 0 0 2 0 1 2 0 0 3 0 0 1 0 0 0|1 3 1 2 4 4 4 1 1
data/example/wavs/004.wav|EN-default|EN|what do you think are some of the benefits of following fashion trends?|_ w ah t d uw y uw th ih ng k aa r s ah m ah V dh ah b eh n ah f ih t s ah V f aa l ow ih ng f ae sh ah n t r eh n d z ? _|0 0 2 0 0 2 0 2 0 2 0 0 2 0 0 2 0 2 0 0 1 0 2 0 1 0 1 0 0 2 0 0 2 0 1 1 0 0 2 0 1 0 0 0 2 0 0 0 0 0|1 3 2 2 4 2 3 2 2 8 2 6 5 6 1 1

サンプルの英語のメタデータファイルだと、正規化テキストだけでなく音素変換したテキストにも句読点は含まれている。あと、余談だが、声調・アクセント情報なんかもきちんとついている(日本語はすべて0)。

このデータを生成しているのは以下の箇所。

https://github.com/myshell-ai/MeloTTS/blob/main/melo/preprocess_text.py#L55

このclean_text_berttext/cleaner.pyにある
https://github.com/myshell-ai/MeloTTS/blob/main/melo/preprocess_text.py#L8

ここ。

https://github.com/myshell-ai/MeloTTS/blob/main/melo/text/cleaner.py#L16-L27

text_normalizeで正規化テキストを生成、その正規化テキストをg2pに渡して音素テキストなどを生成している。でそれぞれのメソッドは各言語ごとに共通のメソッド名になっていて、言語ごとにモジュールを読み出している。

https://github.com/myshell-ai/MeloTTS/blob/main/melo/text/cleaner.py#L1-L6

日本語モジュールはこれ。

https://github.com/myshell-ai/MeloTTS/tree/main/melo/text/japanese.py

text_normalize

https://github.com/myshell-ai/MeloTTS/blob/main/melo/text/japanese.py#L548-L554

単体で実行してみるとわかるのだけど、句読点入りのテキストを渡すと見事に全部の句読点を消してくれる。ただ、replace_punctuationでは日本語全角の句読点を置き換えているように見えるので、意図した動作ではないように思える。

https://github.com/myshell-ai/MeloTTS/blob/main/melo/text/japanese.py#L510-L537

悪さをしているのはis_japanese_character

https://github.com/myshell-ai/MeloTTS/blob/main/melo/text/japanese.py#L488-L507

is_japanese_characterは与えられた文字が日本語Unicodeの範囲内にあるかどうかをチェックする。ただここで指定されている範囲内には句読点が含まれない。。。

試してみる。

def is_japanese_character(char):
    # 日本語のUnicode範囲
    japanese_ranges = [
        (0x3040, 0x309F),  # ひらがな
        (0x30A0, 0x30FF),  # カタカナ
        (0x4E00, 0x9FFF),  # 漢字
        (0x3400, 0x4DBF),  # 漢字拡張A
        (0x20000, 0x2A6DF),  # 漢字拡張B
        # その他の漢字拡張の範囲は必要に応じて追加可能
    ]

    # 文字のUnicodeコードを整数に変換します
    char_code = ord(char)

    # 文字が日本語の範囲に含まれるかを確認
    for start, end in japanese_ranges:
        if start <= char_code <= end:
            return True

    return False

jp_panctuations = [
    "。",  # 句点
    "、",  # 読点
    "?",  # 疑問符
    "!",  # 感嘆符
    ":",  # コロン
    ";",  # セミコロン
    "…",   # 三点リーダー
    ","   # カンマ(全角)
]

for i in jp_panctuations:
    print(f"'{i}':", hex(ord(i)), "->", is_japanese_character(i))
出力
'。': 0x3002 -> False
'、': 0x3001 -> False
'?': 0xff1f -> False
'!': 0xff01 -> False
':': 0xff1a -> False
';': 0xff1b -> False
'…': 0x2026 -> False
',': 0xff0c -> False

なのでこうなる。

text = "おはようございます、太郎さん。今日はなにをする予定ですか?外はとても良いお天気ですよ。"
res = "".join([i for i in text if is_japanese_character(i)])
res
出力
おはようございます太郎さん今日はなにをする予定ですか外はとても良いお天気ですよ

また、句読点がASCII文字だった場合も全部削られてしまう。

text = "おはようございます, 太郎さん. 今日はなにをする予定ですか? 外はとても良いお天気ですよ."
res = "".join([i for i in text if is_japanese_character(i)])
res
出力
おはようございます太郎さん今日はなにをする予定ですか外はとても良いお天気ですよ

よって、メタデータ内のテキストに句読点を含めるのが正解と仮定するならば、この部分を修正する必要があると思われる。

句読点が含まれるUnicode範囲を追加してもいいのだが、句読点については該当のファイル内の2箇所で定義されているので、これに合致するかどうかを判定する処理を追加してしまうのが楽だと思う。

https://github.com/myshell-ai/MeloTTS/blob/main/melo/text/japanese.py#L9

https://github.com/myshell-ai/MeloTTS/blob/main/melo/text/japanese.py#L510-L521

text/japanese.py
def is_japanese_character(char):
    japanese_ranges = [
        (0x3040, 0x309F),
        (0x30A0, 0x30FF),
        (0x4E00, 0x9FFF),
        (0x3400, 0x4DBF),
        (0x20000, 0x2A6DF),
    ]

    char_code = ord(char)

    for start, end in japanese_ranges:
        if start <= char_code <= end:
            return True

    # 追加: 句読点に合致するかを確認
    if char in rep_map or char in punctuation:
        return True

    return False

前処理スクリプトを再度実行してみる。

!python preprocess_text.py --metadata metadata.list

再生成されたmetadata.list.cleanedを見てみると、句読点がきちんと反映されているのがわかる。

metadata.list.cleaned
ita_wav_out/recitation001.wav|Zundamon|JP|オンナノコガキッキッウレシソウ.|_ o N n a n o k o g a k i q k i q u r e sh i s o u . _|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|1 3 3 2 2 2 2 2 2 2 2 2 1 1
ita_wav_out/recitation002.wav|Zundamon|JP|ツァツォニリョコウシタ.|_ ts a ts o n i ry o k o u sh i t a . _|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|1 3 2 2 2 2 2 2 1 1
ita_wav_out/recitation003.wav|Zundamon|JP|ミンシュウガテュルリーキュウデンニシンニュウシタ.|_ m i N sh u u g a ty u r u r i ky u u d e N n i sh i N ny u u sh i t a . _|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|1 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1
ita_wav_out/recitation004.wav|Zundamon|JP|ハイチキョウワコクデトゥーサンルーヴェルテュールガショウリヲオサメラレタノハ,ジッサイオウネツビョウノオカゲダッタ.|_ h a i ch i ky o u w a k o k u d e t u s a N r u b e r u ty u r u g a sh o u r i o o s a m e r a r e t a n o h a , j i q s a i o u n e ts u by o u n o o k a g e d a q t a . _|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|1 5 1 1 2 1 4 2 2 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 1 3 2 2 2 2 2 2 2 2 2 2 2 2 1 1
ita_wav_out/recitation005.wav|Zundamon|JP|レジャンドルハミンシュウヲテュルリーキュウデンニマネイタ.|_ r e j a N d o r u h a m i N sh u u o ty u r u r i ky u u d e N n i m a n e i t a . _|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|1 3 2 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1
ita_wav_out/recitation006.wav|Zundamon|JP|ジョゲンハデキナイトデュパンハイッタ.|_ j o g e N h a d e k i n a i t o dy u p a N h a i q t a . _|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|1 3 3 3 3 3 3 3 2 2 2 1 1
ita_wav_out/recitation007.wav|Zundamon|JP|フランスニンシェフトニホンジンシェフハゼンゼンチガウ.|_ f u r a N s u n i N sh e f u t o n i h o N j i N sh e f u h a z e N z e N ch i g a u . _|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|1 7 3 3 3 3 3 3 2 2 2 2 2 2 2 2 1 1
ita_wav_out/recitation008.wav|Zundamon|JP|チュウゴクノガイコウダンニアタッシェトシテハケンサレタ.|_ ch u u g o k u n o g a i k o u d a N n i a t a q sh e t o sh i t e h a k e N s a r e t a . _|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|1 2 1 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 1
ita_wav_out/recitation009.wav|Zundamon|JP|ファシズムセイリョクトノソウリョクセンニノゾム.|_ f a sh i z u m u s e i ry o k u t o n o s o u ry o k u s e N n i n o z o m u . _|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|1 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 1 1
ita_wav_out/recitation010.wav|Zundamon|JP|カグショウニンノフィシェルハ,ニグルマトコウマヲカシテクレタ.|_ k a g u sh o u n i N n o f i sh e r u h a , n i g u r u m a t o k o u m a o k a sh i t e k u r e t a . _|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|1 3 3 3 3 2 2 2 2 1 3 3 3 3 3 3 2 2 2 2 2 1 1
(snip)

ただし、繰り返しになるが、これはあくまでも自分の所感と仮説に基づいた修正であって、本当にこれが事象の原因なのか?対処法として正しいのかどうか?はわかっていないという点に留意されたい。

kun432kun432

もう1つ。

あと、余談だが、声調・アクセント情報なんかもきちんとついている(日本語はすべて0)。

なのだが、ここは正規化したテキストをg2pに渡すことで取得している。

https://github.com/myshell-ai/MeloTTS/blob/main/melo/text/cleaner.py#L16-L27

日本語のg2pはここにある

https://github.com/myshell-ai/MeloTTS/blob/main/melo/text/japanese.py#L571-L612

声調・アクセント情報はtonesに入るのだが、ここ、単に音素の数だけ0埋めしてるだけに見える。

https://github.com/myshell-ai/MeloTTS/blob/main/melo/text/japanese.py#L609

ちなみに英語の場合はどうやら音素ごとのアクセントの辞書が用意されていて、ここから取得している様子。

https://github.com/myshell-ai/MeloTTS/blob/main/melo/text/english.py#L190-L215

つまり、現状、日本語の場合はこの部分でアクセント情報を付与することはできない(対応していない)のではないかと思うが、ただ、学習する音声データには当然アクセントが含まれているので多少なりとも反映されるのではないかなーという気はしている。

もしここにアクセント情報を付与するならば、このあたりを参考にg2pを修正する必要があるのではないかと思う。

https://zenn.dev/syoyo/articles/65265572b9e3f0

https://www.gavo.t.u-tokyo.ac.jp/ojad/

https://www.negi.moe/negitalk/openjtalk.html

今回はそこまでは触れないこととする。

kun432kun432

ということで少し脱線したけども、学習を行う。学習スクリプトは学習中のログや結果をTensorboardで確認できるようになっているのだが、matplotlibのインストールが必要になる。最新のmatplotlibだとエラーになったため、古いバージョンを指定している。

!pip install matplotlib==3.7.0

では、スクリプトを実行する。最後の数字はGPUの数を指定する。

!bash train.sh config.json 1

いろいろwarningが出るが、以下のような感じで進んでいればおそらくOKだと思う。

出力
100%|███████████████████████████████████████████| 51/51 [00:46<00:00,  1.10it/s]
100%|███████████████████████████████████████████| 51/51 [00:19<00:00,  2.68it/s]
 65%|███████████████████████████▊               | 33/51 [00:11<00:03,  4.73it/s]

ターミナルを開いてみると、logsディレクトリが作成されていて、ここに学習ログや学習済みチェックポイント(G_*.pth)が生成されていく模様。

tree logs
出力
logs
├── DUR_0.pth
├── D_0.pth
├── G_0.pth
├── config.json
├── eval
│   └── events.out.tfevents.1735438561.rtx4090.local.616308.1
├── events.out.tfevents.1735438561.rtx4090.local.616308.0
└── train.log

1 directory, 7 files

train.logを見るとこんな感じで学習が進んでいるのがわかる。学習済みチェックポイント(G_*.pth)は新しいものが作成されると、定期的に古いものが削除される様子。

train.log
2024-12-29 11:16:09,595 .       INFO    Train Epoch: 1 [0%]
2024-12-29 11:16:09,596 .       INFO    [2.6705377101898193, 2.5818519592285156, 7.6550822257995605, 32.665504455566406, 4.894428253173828, 5.4106011390686035, 0, 0.0003]
2024-12-29 11:16:14,075 .       INFO    Saving model and optimizer state at iteration 1 to ./logs/./G_0.pth
2024-12-29 11:16:14,546 .       INFO    Saving model and optimizer state at iteration 1 to ./logs/./D_0.pth
2024-12-29 11:16:14,844 .       INFO    Saving model and optimizer state at iteration 1 to ./logs/./DUR_0.pth
2024-12-29 11:16:51,727 .       INFO    ====> Epoch: 1
2024-12-29 11:17:10,765 .       INFO    ====> Epoch: 2
2024-12-29 11:17:26,150 .       INFO    ====> Epoch: 3
2024-12-29 11:17:39,073 .       INFO    Train Epoch: 4 [92%]
2024-12-29 11:17:39,074 .       INFO    [2.544696569442749, 2.4188737869262695, 6.45208215713501, 20.825002670288086, 2.8086748123168945, 2.5876758098602295, 200, 0.00029988751406191406]
2024-12-29 11:17:39,993 .       INFO    ====> Epoch: 4
2024-12-29 11:17:53,244 .       INFO    ====> Epoch: 5
2024-12-29 11:18:06,798 .       INFO    ====> Epoch: 6
2024-12-29 11:18:19,514 .       INFO    ====> Epoch: 7
2024-12-29 11:18:30,968 .       INFO    Train Epoch: 8 [84%]
2024-12-29 11:18:30,969 .       INFO    [2.4857823848724365, 2.320671319961548, 4.911778926849365, 19.896093368530273, 2.782498836517334, 2.3740234375, 400, 0.00029973759841699473]
2024-12-29 11:18:32,550 .       INFO    ====> Epoch: 8
2024-12-29 11:18:45,622 .       INFO    ====> Epoch: 9
2024-12-29 11:18:58,182 .       INFO    ====> Epoch: 10
2024-12-29 11:19:10,081 .       INFO    ====> Epoch: 11
2024-12-29 11:19:22,722 .       INFO    Train Epoch: 12 [76%]
2024-12-29 11:19:22,723 .       INFO    [2.699383497238159, 2.194875478744507, 6.295555114746094, 19.5850830078125, 2.6939125061035156, 2.769932746887207, 600, 0.00029958775771584436]
2024-12-29 11:19:25,101 .       INFO    ====> Epoch: 12
2024-12-29 11:19:38,296 .       INFO    ====> Epoch: 13
2024-12-29 11:19:51,035 .       INFO    ====> Epoch: 14
2024-12-29 11:20:03,920 .       INFO    ====> Epoch: 15
2024-12-29 11:20:12,748 .       INFO    Train Epoch: 16 [69%]
2024-12-29 11:20:12,749 .       INFO    [2.5052032470703125, 2.12357234954834, 5.110225677490234, 15.397970199584961, 2.557176351547241, 2.0657870769500732, 800, 0.00029943799192099825]
2024-12-29 11:20:15,898 .       INFO    ====> Epoch: 16
2024-12-29 11:20:28,843 .       INFO    ====> Epoch: 17
2024-12-29 11:20:40,821 .       INFO    ====> Epoch: 18
2024-12-29 11:20:53,605 .       INFO    ====> Epoch: 19
2024-12-29 11:21:01,319 .       INFO    Train Epoch: 20 [61%]
2024-12-29 11:21:01,320 .       INFO    [2.2758665084838867, 2.6695761680603027, 8.429858207702637, 23.7624568939209, 2.5019595623016357, 2.4719488620758057, 1000, 0.00029928830099501015]
2024-12-29 11:21:04,160 .       INFO    Saving model and optimizer state at iteration 20 to ./logs/./G_1000.pth
2024-12-29 11:21:04,559 .       INFO    Saving model and optimizer state at iteration 20 to ./logs/./D_1000.pth
2024-12-29 11:21:04,863 .       INFO    Saving model and optimizer state at iteration 20 to ./logs/./DUR_1000.pth
2024-12-29 11:21:08,610 .       INFO    ====> Epoch: 20
2024-12-29 11:21:20,426 .       INFO    ====> Epoch: 21

ターミナルからTensorboardを起動してみる。

tensorboard --logdir=./logs --host 0.0.0.0 --port 6006

ブラウザを開いて、6006番ポートにアクセスすると以下のようなグラフが確認できる。

いろいろグラフは多数表示される。自分もあまり詳しくはないのだが、以下あたりを見ればいい様子。

  • loss/d/total: 一定の範囲内で推移しているか、極端に上下していないか。
  • loss/g/total: 順調に減少しているか。ある程度学習が進むと同じぐらいのところで推移するようになる。

学習中にも試すことはできる。別のノートブックを開いて以下の様な形で実行する。

from melo.api import TTS
from IPython.display import Audio, display

speed = 1.0
device = 'cpu'

text = "おはようございます!今日は良いお天気ですね。競馬観戦にはもってこいですね!"
model = TTS(
    language='JP',  # メタデータファイルで指定した言語コードを指定
    config_path="config.json",  # config.jsonのパスを指定
    ckpt_path="logs/G_4000.pth",  # 学習途中のチェックポイントのパスを指定
    device=device
)
speaker_ids = model.hps.data.spk2id

model.tts_to_file(
    text,
    speaker_ids['Zundamon'],  # メタデータファイルで指定したスピーカーIDを指定
    "training.wav",
    speed=speed
)
display(Audio("training.wav", autoplay=True))

学習時間15分ぐらい・イテレーション4000/エポック80ぐらいのデータでこんな感じになる。

https://audio.com/kun432/audio/melotts-jp-trained-sample-1

発話がまだ一部怪しいところもあるが、この短時間でもかなりそれっぽく学習できているのがわかる。前処理で無音を考慮したところもそれなりに上手くできているのではないかと思う。

あとはTensorboardの推移を見つつ、定期的に新しいチェックポイントで生成してチェックすれば良いと思う。

なお、学習用スクリプトはおそらくconfig.jsonで指定されているエポックまで学習継続すると思う(特に指定していないのでデフォルトは10000)が、十分学習できていると判断できたら停止して、チェックポイントとconfig.jsonを保存しておけば良い。

kun432kun432

補足

  • データ量が多いほうが精度が上がりそうな気がする。その分学習時間も掛かりそうだけど。
    • 例えば今回の場合だと、ROHAN4600マルチモーダルデータベースの音声データを使うと良さそう。
  • 試していないけども、メタデータファイルは複数言語・複数スピーカーの設定を行えそう。
    • おそらく公式の英語音声は複数スピーカーで学習されていると思う。
    • 例えば今回の場合だと、ずんだもんの複数の感情音声とか、他のキャラクターの音声とかも、1つのモデルとして学習して設定で切り替えるということができそうに思う
  • 音素変換するために一旦カタカナに変換しているのだが、ここでミスる場合がある
    • 元の文: 「彼女と初デートの今日は夢うつつ。」
    • 変換後の文: 「カノジョトハツデートノコンニチハユメウツツ.」
    • kakasiで単純にカタカナ変換していて失敗している。ここもいろいろ改善の余地はありそう。
    • 前後の文によっては正しく変換されることもあると思うので、データ量が多ければカバーできるかもしれないと思う。

学習中のVRAM使用量は5〜9GBあたりかな。

Sun Dec 29 12:06:18 2024
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 560.35.03              Driver Version: 560.35.03      CUDA Version: 12.6     |
|-----------------------------------------+------------------------+----------------------+
| GPU  Name                 Persistence-M | Bus-Id          Disp.A | Volatile Uncorr. ECC |
| Fan  Temp   Perf          Pwr:Usage/Cap |           Memory-Usage | GPU-Util  Compute M. |
|                                         |                        |               MIG M. |
|=========================================+========================+======================|
|   0  NVIDIA GeForce RTX 4090        Off |   00000000:01:00.0 Off |                  Off |
| 31%   49C    P0            157W /  450W |    8208MiB /  24564MiB |     82%      Default |
|                                         |                        |                  N/A |
+-----------------------------------------+------------------------+----------------------+
kun432kun432

まとめ

少し手を入れたものの、そのためにコードをいろいろ追いかけたことで、音声処理に関する知見も得れた気がしている。また、学習についても、いろいろなTTSフレームワークがある中で比較的容易にできるのではないかと思う。

このスクラップは2024/12/29にクローズされました